Cloud  /  Terraform

IaC Terraform 50 guides · updated 2026

Infrastructure as code done right — providers, state, reusable modules, and the workflow patterns that keep multi-cloud deployments sane in 2026.

Compliance as Code in Terraform

Compliance as Code means encoding security standards, regulatory requirements, and organizational policies directly into the Terraform workflow — automatically enforced at plan time rather than discovered during audits or after incidents.


The Compliance Pipeline Layer

Code commit
↓ terraform fmt + validate
↓ tfsec / Checkov (static analysis — catches 95% of issues instantly)
↓ OPA / Conftest (custom policy rules)
↓ Sentinel (HashiCorp's policy engine, Terraform Cloud/Enterprise)
↓ terraform plan (with cost estimation)
↓ Human review of plan + policy report
↓ terraform apply (only if all gates pass)

Checkov: Open-Source Static Analysis

Checkov scans Terraform files for hundreds of predefined security misconfigurations:

Terminal window
# Install
pip install checkov
# Scan Terraform directory
checkov -d ./infrastructure
# Scan and output JUnit XML for CI
checkov -d ./infrastructure --output junitxml > checkov-results.xml
# Scan a specific file
checkov -f main.tf
# Run only specific checks
checkov -d . --check CKV_AWS_23,CKV_AWS_116
# Example output:
# Check: CKV_AWS_23: "Ensure every security groups rule has a description"
# FAILED for resource: aws_security_group.web
# File: /infrastructure/main.tf:45
#
# Check: CKV_AWS_116: "Ensure that AWS Lambda function is configured for a DLQ"
# FAILED for resource: aws_lambda_function.processor

tfsec: Terraform-Specific Security Scanner

Terminal window
# Install
brew install tfsec
# Scan
tfsec ./infrastructure
# With severity threshold (fail only on HIGH/CRITICAL)
tfsec ./infrastructure --minimum-severity HIGH
# JSON output for CI
tfsec ./infrastructure --format json | jq '.results | length'

Common issues tfsec catches:


OPA / Conftest: Custom Policy Rules

Open Policy Agent with Conftest lets you write custom policies in Rego:

Terminal window
# Install conftest
brew install conftest
# Generate JSON plan
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
# Test policies against plan
conftest test tfplan.json --policy ./policies/
policies/required_tags.rego
package main
required_tags = {"Environment", "Team", "CostCenter", "ManagedBy"}
deny[msg] {
resource := input.resource_changes[_]
resource.change.actions[_] == "create"
resource.type == "aws_instance"
actual_tags := {k | resource.change.after.tags[k]}
missing := required_tags - actual_tags
count(missing) > 0
msg := sprintf(
"Resource %s is missing required tags: %v",
[resource.address, missing]
)
}
policies/no_public_access.rego
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_s3_bucket_public_access_block"
resource.change.after.block_public_acls == false
msg := sprintf(
"S3 bucket %s must have block_public_acls = true",
[resource.address]
)
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_security_group_rule"
resource.change.after.cidr_blocks[_] == "0.0.0.0/0"
resource.change.after.from_port <= 22
resource.change.after.to_port >= 22
msg := sprintf(
"Security group rule %s allows SSH from the public internet",
[resource.address]
)
}

Sentinel: HashiCorp’s Policy Engine

Available in Terraform Cloud and Terraform Enterprise, Sentinel policies are enforced server-side before any apply can proceed:

sentinel/require-encryption.sentinel
import "tfplan/v2" as tfplan
# All EBS volumes must be encrypted
ebs_encrypted = rule {
all tfplan.resource_changes as _, change {
change.type is not "aws_ebs_volume" or
change.change.after.encrypted is true
}
}
# All RDS instances must use encrypted storage
rds_encrypted = rule {
all tfplan.resource_changes as _, change {
change.type is not "aws_db_instance" or
change.change.after.storage_encrypted is true
}
}
main = rule {
ebs_encrypted and rds_encrypted
}

Terraform Compliant Resource Examples

Writing resources that pass common compliance checks from the start:

# Compliant S3 bucket
resource "aws_s3_bucket" "data" {
bucket = "my-compliant-bucket"
tags = local.required_tags
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "data" {
bucket = aws_s3_bucket.data.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "aws:kms"
}
}
}
resource "aws_s3_bucket_versioning" "data" {
bucket = aws_s3_bucket.data.id
versioning_configuration {
status = "Enabled"
}
}
# Compliant security group — no 0.0.0.0/0 on SSH
resource "aws_security_group" "app" {
name = "app-sg"
description = "Application security group — web traffic only"
vpc_id = aws_vpc.main.id
ingress {
description = "HTTPS from load balancer only"
from_port = 443
to_port = 443
protocol = "tcp"
security_groups = [aws_security_group.alb.id]
}
egress {
description = "All outbound traffic"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

CI/CD Compliance Gate

.github/workflows/compliance.yml
- name: Run Checkov
run: |
checkov -d infrastructure/ \
--output github_failed_only \
--soft-fail-on MEDIUM \
--hard-fail-on HIGH,CRITICAL
- name: Run tfsec
run: tfsec infrastructure/ --minimum-severity HIGH
- name: Run Conftest
run: |
terraform plan -out=tfplan
terraform show -json tfplan > tfplan.json
conftest test tfplan.json --policy policies/

All three tools must pass before the apply job can run. This creates an automated compliance gate that requires zero manual effort on every infrastructure change.