Infrastructure as Code Security
Infrastructure as Code brings versioning and automation benefits but introduces risks if misconfigurations are propagated automatically. Security must be shift-left for IaC.
Common Vulnerabilities in IaC
- Hardcoded secrets: Credentials in versioned code
- Overly permissive rules: Security groups 0.0.0.0/0
- Unencrypted resources: S3 buckets, RDS without encryption
- Public access: Resources exposed unnecessarily
- Missing logging: CloudTrail, flow logs disabled
- Weak authentication: MFA not enforced
Scanning Tools
Terraform - tfsec, Checkov, Terrascan
# Vulnerability example - public S3 bucket
resource "aws_s3_bucket" "bad_bucket" {
bucket = "my-public-bucket"
acl = "public-read" # [ERROR] VULNERABLE
}
# Fix
resource "aws_s3_bucket" "good_bucket" {
bucket = "my-private-bucket"
acl = "private" # [OK] Secure
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
versioning {
enabled = true
}
}
resource "aws_s3_bucket_public_access_block" "good_bucket" {
bucket = aws_s3_bucket.good_bucket.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
tfsec - Terraform Scanning
# Install
brew install tfsec
# Scan Terraform files
tfsec .
# Specific output
tfsec --format json --out results.json .
# CI/CD integration
tfsec --soft-fail . || exit 1
Checkov - Multi-Cloud IaC Scanner
# Install
pip install checkov
# Scan Terraform
checkov -d ./terraform
# Scan CloudFormation
checkov -f template.yaml
# Scan Kubernetes manifests
checkov -d ./k8s
# Skip specific checks
checkov -d . --skip-check CKV_AWS_20
# Custom policies
checkov -d . --external-checks-dir ./custom-policies
Policy as Code
Open Policy Agent (OPA)
OPA lets you write policies in the Rego language to enforce compliance:
# deny_public_s3.rego
package terraform.aws.s3
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
resource.change.after.acl == "public-read"
msg := sprintf("S3 bucket %s cannot be public", [resource.address])
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket"
not resource.change.after.server_side_encryption_configuration
msg := sprintf("S3 bucket %s must have encryption enabled", [resource.address])
}
Sentinel (HashiCorp)
Policy framework integrated with Terraform Cloud/Enterprise:
# enforce-mandatory-tags.sentinel
import "tfplan/v2" as tfplan
mandatory_tags = ["Environment", "Owner", "Project"]
all_resources = filter tfplan.resource_changes as _, rc {
rc.mode is "managed"
}
deny_resources_without_tags = rule {
all all_resources as _, resource {
all mandatory_tags as tag {
resource.change.after.tags contains tag
}
}
}
Secrets Management in IaC
Terraform - Using AWS Secrets Manager
# Create secret in Secrets Manager
resource "aws_secretsmanager_secret" "db_password" {
name = "production/db/password"
recovery_window_in_days = 30
}
resource "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
secret_string = random_password.db_password.result
}
# Reference secret (does not expose the value)
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
}
# Use in RDS (reference, not hardcoded value)
resource "aws_db_instance" "main" {
# ... other configs
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}
git-secrets - Prevent Committing Secrets
# Install git-secrets
brew install git-secrets
# Setup hooks for repository
git secrets --install
git secrets --register-aws
# Scan commit history
git secrets --scan-history
# Prevent commits with secrets
# Automatic via pre-commit hook
State File Security
- Remote state: S3 + DynamoDB lock, never commit local state
- Encryption: Server-side encryption on S3
- Access control: Restrictive IAM policies for the state bucket
- Versioning: Enable versioning for recovery
- State locking: DynamoDB to prevent concurrent modifications
Secure Terraform Remote Backend
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "global/s3/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "terraform-state-lock"
# MFA delete protection
versioning {
enabled = true
mfa_delete = true
}
}
}
CI/CD Integration
GitHub Actions - Secure Terraform Workflow
name: 'Terraform Security Scan'
on:
pull_request:
branches: [ main ]
jobs:
terraform-security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run tfsec
uses: aquasecurity/[email protected]
with:
soft_fail: false
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
output_format: sarif
output_file_path: checkov.sarif
- name: Upload results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: checkov.sarif
Drift Detection
Detect manual changes outside of IaC that create security gaps:
- Terraform Cloud: Automatic health checks
- driftctl: Open-source drift detection
- CloudFormation Drift Detection: Native AWS service
- Scheduled scans: Periodic CI/CD jobs
Compliance Frameworks
- CIS Benchmarks: Terraform modules for compliance
- NIST 800-53: Policy packs for controls
- PCI-DSS: Policies for payment card processing
- HIPAA: Healthcare compliance policies
- SOC 2: Security organization policies
Secure Terraform Modules
- Use verified Terraform Registry modules
- Pin module versions (do not use latest)
- Code review of internal modules
- Private registry for custom modules
- Document security considerations
Best Practices
- Peer review: Mandatory PRs for production changes
- Plan before apply: Review proposed changes
- Separate environments: Isolated Dev/Staging/Prod
- Tagging strategy: Tags for governance and cost allocation
- Least privilege: Specific IAM roles for CI/CD
- Automated testing: Terratest for validation
Final Recommendations
IaC security must be automated and integrated into CI/CD from the start. Use multiple scanning tools (tfsec, Checkov) for complete coverage. Implement policy as code with OPA or Sentinel. Never commit secrets, use secret managers. Continuously monitor drift. IaC misconfiguration is one of the most common causes of cloud breaches.
