Terraform Security
IaC Security & Compliance 2026
tfsec, checkov, State Encryption, Sentinel, Least Privilege IAM
tfseccheckovSentinelState Encryption
State Security
# backend.tf - Remote Backend with Encryption
terraform {
backend "s3" {
bucket = "terraform-state-prod"
key = "infrastructure/terraform.tfstate"
region = "eu-central-1"
encrypt = true
kms_key_id = "arn:aws:kms:eu-central-1:123:key/terraform"
# State locking
dynamodb_table = "terraform-locks"
# Additional protection
acl = "private"
}
}
# State encryption at rest (Terraform Cloud)
terraform {
cloud {
organization = "company"
workspaces {
name = "production"
}
}
}tfsec CI/CD Integration
# .github/workflows/terraform-security.yml
name: Terraform Security
on: [push, pull_request]
jobs:
tfsec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run tfsec
uses: aquasecurity/tfsec-action@v1.0.0
with:
soft_fail: false
checkov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Checkov
uses: bridgecrewio/checkov-action@master
with:
directory: .
framework: terraform
soft_fail: false
terraform-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init -backend=false
- name: Terraform Validate
run: terraform validateLeast Privilege IAM
# terraform-deployer-role.tf - Minimal Permissions
data "aws_iam_policy_document" "terraform_deployer" {
statement {
sid = "EC2Minimal"
effect = "Allow"
actions = [
"ec2:DescribeInstances",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceCreditSpecifications",
]
resources = ["*"]
}
statement {
sid = "S3StateAccess"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:PutObject",
]
resources = [
"arn:aws:s3:::terraform-state-prod/*"
]
}
statement {
sid = "DynamoDBLocking"
effect = "Allow"
actions = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem",
]
resources = [
"arn:aws:dynamodb:*:*:table/terraform-locks"
]
}
}Sentinel Policies (Terraform Cloud)
# require-private-s3.sentinel
import "tfplan"
# Ensure all S3 buckets are private
s3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
rc.mode is "managed"
}
bucket_is_private = rule when s3_buckets is not empty {
all s3_buckets as _, bucket {
bucket.change.after.acl is "private"
}
}
main = rule {
bucket_is_private
}
# enforce-tags.sentinel - Require Cost Center Tags
required_tags = ["CostCenter", "Environment", "Owner"]
resources = filter tfplan.resource_changes as _, rc {
rc.mode is "managed"
}
has_required_tags = rule {
all resources as _, r {
all required_tags as tag {
r.change.after.tags contains tag
}
}
}