Skip to main content
Source

This page is generated from skills/terraform-skill/references/security-compliance.md. Edit the source, not this page.

Third-party skill

This skill is maintained by Anton Babenko (terraform-best-practices.com, Compliance.tf) under the Apache-2.0 license. Upstream: https://github.com/antonbabenko/terraform-skill

Security & Compliance

Part of: terraform-skill Purpose: Security best practices and compliance patterns for Terraform/OpenTofu

This document provides security hardening guidance and compliance automation strategies for infrastructure-as-code.


Table of Contents

  1. Security Scanning Tools
  2. Common Security Issues
  3. Compliance Testing
  4. Secrets Management
  5. State File Security

Security Scanning Tools

Essential Security Checks

# Static security scanning
trivy config .
checkov -d .

# Compliance testing
terraform-compliance -f compliance/ -p tfplan.json

Trivy Integration

Install:

# macOS
brew install trivy

# Linux
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin

# In CI
- uses: aquasecurity/trivy-action@master
with:
scan-type: 'config'
scan-ref: '.'

Note: Trivy is the successor to tfsec, maintained by Aqua Security.

Example Output:

Result #1 HIGH Security group rule allows egress to multiple public internet addresses
────────────────────────────────────────────────────────────────────────────────
security.tf:15-20

12 | resource "aws_security_group_rule" "egress" {
13 | type = "egress"
14 | from_port = 0
15 | to_port = 0
16 | protocol = "-1"
17 | cidr_blocks = ["0.0.0.0/0"]
18 | security_group_id = aws_security_group.this.id
19 | }

Checkov Integration

# Run Checkov
checkov -d . --framework terraform

# Skip specific checks
checkov -d . --skip-check CKV_AWS_23

# Generate JSON report
checkov -d . -o json > checkov-report.json

Common Security Issues

❌ DON'T: Store Secrets in Variables

# BAD: Secret in plaintext
variable "database_password" {
type = string
default = "SuperSecret123!" # ❌ Never do this
}

✅ DO: Use Secrets Manager

# Good: Reference secrets from AWS Secrets Manager
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = "prod/database/password"
}

resource "aws_db_instance" "this" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
}

❌ DON'T: Use Default VPC

# BAD: Default VPC has public subnets
resource "aws_instance" "app" {
ami = "ami-12345"
subnet_id = "subnet-default" # ❌ Avoid default resources
}

✅ DO: Create Dedicated VPCs

# Good: Custom VPC with private subnets
resource "aws_vpc" "this" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
}

resource "aws_subnet" "private" {
vpc_id = aws_vpc.this.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}

❌ DON'T: Skip Encryption

# BAD: Unencrypted S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
# ❌ No encryption configured
}

✅ DO: Enable Encryption at Rest

# Good: Enable encryption
resource "aws_s3_bucket" "data" {
bucket = "my-data-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

❌ DON'T: Open Security Groups to Internet

# BAD: Security group open to internet
resource "aws_security_group_rule" "allow_all" {
type = "ingress"
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # ❌ Never do this
security_group_id = aws_security_group.this.id
}

✅ DO: Use Least-Privilege Security Groups

# Good: Restrict to specific ports and sources
resource "aws_security_group_rule" "app_https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # ✅ Internal only
security_group_id = aws_security_group.this.id
}

Compliance Testing

terraform-compliance

Install:

pip install terraform-compliance

Example Compliance Test:

# compliance/aws-encryption.feature
Feature: AWS Resources must be encrypted

Scenario: S3 buckets must have encryption
Given I have aws_s3_bucket defined
When it has aws_s3_bucket_server_side_encryption_configuration
Then it must contain rule
And it must contain apply_server_side_encryption_by_default

Scenario: RDS instances must be encrypted
Given I have aws_db_instance defined
Then it must contain storage_encrypted
And its value must be true

Run Tests:

# Generate plan in JSON
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json

# Run compliance tests
terraform-compliance -f compliance/ -p tfplan.json

Open Policy Agent (OPA)

# policy/s3_encryption.rego
package terraform.s3

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])
}

Secrets Management

AWS Secrets Manager Pattern

# Create secret
resource "aws_secretsmanager_secret" "db_password" {
name = "prod/database/password"
description = "RDS master 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
}

# Generate secure password
resource "random_password" "db_password" {
length = 32
special = true
}

# Use secret in RDS
data "aws_secretsmanager_secret_version" "db_password" {
secret_id = aws_secretsmanager_secret.db_password.id
}

resource "aws_db_instance" "this" {
password = data.aws_secretsmanager_secret_version.db_password.secret_string
# ...
}

Environment Variables

# Never commit these
export TF_VAR_database_password="secret123"
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"

In .gitignore:

*.tfvars
.env
secrets/

State File Security

Encrypt State at Rest

# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "terraform-locks"
encrypt = true # ✅ Always enable encryption
}
}

Secure State Bucket

resource "aws_s3_bucket" "terraform_state" {
bucket = "my-terraform-state"
}

# Enable versioning (protect against accidental deletion)
resource "aws_s3_bucket_versioning" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id

versioning_configuration {
status = "Enabled"
}
}

# Enable encryption
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id

rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}

# Block public access
resource "aws_s3_bucket_public_access_block" "terraform_state" {
bucket = aws_s3_bucket.terraform_state.id

block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}

Restrict State Access

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/TerraformRole"
},
"Action": [
"s3:ListBucket",
"s3:GetObject",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::my-terraform-state",
"arn:aws:s3:::my-terraform-state/*"
]
}
]
}

IAM Best Practices

✅ DO: Use Least Privilege

# Good: Specific permissions only
resource "aws_iam_policy" "app_policy" {
name = "app-policy"

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject"
]
Resource = "arn:aws:s3:::my-app-bucket/*"
}
]
})
}

❌ DON'T: Use Wildcard Permissions

# BAD: Overly broad permissions
resource "aws_iam_policy" "bad_policy" {
policy = jsonencode({
Statement = [
{
Effect = "Allow"
Action = "*" # ❌ Never use wildcard
Resource = "*"
}
]
})
}

Compliance Checklists

SOC 2 Compliance

  • Encryption at rest for all data stores
  • Encryption in transit (TLS/SSL)
  • IAM policies follow least privilege
  • Logging enabled for all resources
  • MFA required for privileged access
  • Regular security scanning in CI/CD

HIPAA Compliance

  • PHI encrypted at rest and in transit
  • Access logs enabled
  • Dedicated VPC with private subnets
  • Regular backup and retention policies
  • Audit trail for all infrastructure changes

PCI-DSS Compliance

  • Network segmentation (separate VPCs)
  • No default passwords
  • Strong encryption algorithms
  • Regular security scanning
  • Access control and monitoring

Resources


Back to: Main Skill File