Introduction

Configuration drift — when actual system configuration diverges from the intended secure baseline — is a leading cause of security incidents. Secure configuration management ensures that systems remain in a known, compliant state throughout their lifecycle. This requires automation at every stage: validation at build time, enforcement at deploy time, and detection at runtime.
Infrastructure as Code Scanning
IaC scanning catches misconfigurations before they reach production.
Checkov: scan Terraform for security issues
checkov -d terraform/environments/production
tfsec: Terraform security scanner
tfsec terraform/environments/production --config-file tfsec.yaml
kics: Keep Infrastructure as Code Secure
kics scan -p kubernetes/deployments --output-path kics-report.json
checkov policy: S3 bucket must have encryption enabled
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
This will fail checkov check CKV_AWS_21
Missing: server_side_encryption_configuration
}
Custom Checkov policy
from checkov.common.models.enums import CheckResult
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class S3EncryptionCheck(BaseResourceCheck):
def init(self):
name = "Ensure S3 bucket has encryption enabled"
id = "CKV_CUSTOM_001"
supported_resources = ['aws_s3_bucket']
super().init(name=name, id=id, supported_resources=supported_resources)
def scan_resource_conf(self, conf):
if 'server_side_encryption_configuration' in conf:
return CheckResult.PASSED
return CheckResult.FAILED
Drift Detection
Drift detection identifies when live infrastructure differs from the declared configuration.
Terraform plan detects drift
terraform plan -refresh-only # Check for manual changes
Terraform drift detection with AWS Config
resource "aws_config_config_rule" "s3_bucket_ssl" {
name = "s3-bucket-ssl-requests-only"
source {
owner = "AWS"
source_identifier = "S3_BUCKET_SSL_REQUESTS_ONLY"
}
}
Continuous drift monitoring
1\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Schedule terraform plan to run daily
2\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Compare output with baseline
3\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\. Alert on unexpected changes
!/bin/bash
drift_detection.sh
BASELINE_DIR="/config/baselines"
REPORT_DIR="/config/reports"
for env in production staging; do
cd terraform/environments/$env
terraform plan -refresh-only -out=plan.tfplan 2>&1 | \
grep -E "changed|destroyed|added" > /tmp/drift.txt
if [ -s /tmp/drift.txt ]; then
Send alert
./send_alert.sh "Drift detected in $env environment"
cp /tmp/drift.txt "$REPORT_DIR/${env}drift $(date +%Y%m%d).txt"
fi
done
Configuration Validation Pipeline
CI/CD configuration validation stage
stages:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- validate
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- scan
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- deploy
validate:
stage: validate
script:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- terraform fmt -check
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- terraform validate
scan:
stage: scan
script:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- checkov -d . -o json > checkov-report.json
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- tfsec . --format sarif > tfsec-report.sarif
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- conftest test . --policy policies/
artifacts:
reports:
checkov: checkov-report.json
tfsec: tfsec-report.sarif
deploy:
stage: deploy
script:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- terraform apply -auto-approve
only:
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\- main
when: manual
Configuration Validation with Conftest
Conftest applies policy-as-code to configuration files using Open Policy Agent (OPA).
conftest policy for Kubernetes
package main
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.containers[_].securityContext.runAsNonRoot
msg = "Containers must run as non-root"
}
deny[msg] {
input.kind == "Deployment"
input.spec.template.spec.containers[_].securityContext.privileged == true
msg = "Privileged containers are not allowed"
}
deny[msg] {
input.kind == "Service"
input.spec.type == "LoadBalancer"
msg = "LoadBalancer services are not allowed"
}
deny[msg] {
input.kind == "Pod"
not input.spec.containers[_].resources.limits.memory
msg = "Memory limits are required"
}
Run conftest policies
conftest test deployment.yaml --policy conftest/policies/
Policy as Code with OPA
OPA policy for Terraform
package terraform
deny[msg] {
resource := input.resource.aws_security_group[_]
rule := resource.ingress[_]
rule.from_port == 22
rule.cidr_blocks[_] == "0.0.0.0/0"
msg = sprintf("Security group %v allows SSH from anywhere", [resource.name])
}
deny[msg] {
resource := input.resource.aws_s3_bucket[_]
resource.acl == "public-read" || resource.acl == "public-read-write"
msg = sprintf("S3 bucket %v has public ACL", [resource.name])
}
Secrets in Configuration
Never hardcode secrets in configuration. Use a secrets manager.
BAD: Hardcoded secret
resource "aws_db_instance" "database" {
username = "admin"
password = "P@ssw0rd123!" # NEVER hardcode
}
GOOD: Secrets Manager reference
data "aws_secretsmanager_secret_version" "db_creds" {
secret_id = "production/database/credentials"
}
resource "aws_db_instance" "database" {
username = jsondecode(
data.aws_secretsmanager_secret_version.db_creds.secret_string
)["username"]
password = jsondecode(
data.aws_secretsmanager_secret_version.db_creds.secret_string
)["password"]
}
Conclusion
Secure configuration management requires automation at every stage: scan IaC before deployment, validate against policy as code, detect drift continuously, and never embed secrets in configuration. Shift security left by validating configuration at build time rather than discovering issues in production. Use tools like Checkov, tfsec, conftest, and OPA to enforce your security baseline automatically.
Enjoy this article? Share your thoughts, questions, or experiences in the comments below — your insights help other readers too.
Join the discussion ↓