Terraform and SOC 2: Using Infrastructure as Code to Automate Compliance Controls
Infrastructure as code is the most effective way to achieve continuous SOC 2 compliance. Learn how to map Terraform resources to Trust Services Criteria, use policy as code, and generate audit evidence automatically.
Why IaC Is the Best Foundation for SOC 2
SOC 2 Type II requires evidence that controls operated effectively over a period of 6–12 months. For most engineering teams, the hardest part of Type II isn't implementing controls — it's maintaining them consistently and collecting evidence continuously.
Infrastructure as code solves both problems. When your security controls are defined in Terraform, they're enforced every time infrastructure is provisioned or modified. Configuration drift — a server missing a required tag, an S3 bucket created without encryption, a security group with an overly broad rule — can't persist silently because the IaC configuration is the source of truth.
Mapping Terraform Resources to SOC 2 Criteria
SOC 2's Common Criteria map directly to infrastructure configuration decisions.
CC6 — Logical and Physical Access Controls
CC6.1 restricts access to authorized individuals. In Terraform, use explicit trust policies with no wildcard principals:
resource "aws_iam_role" "app_role" {
name = "app-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
Action = "sts:AssumeRole"
}]
})
}
CC6.7 requires encryption at rest and in transit:
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"
kms_master_key_id = aws_kms_key.data_key.arn
}
}
}
resource "aws_s3_bucket_public_access_block" "data" {
bucket = aws_s3_bucket.data.id
block_public_acls = true
block_public_policy = true
restrict_public_buckets = true
}
CC7 — System Monitoring
GuardDuty and CloudTrail should be provisioned as code, not configured manually:
resource "aws_guardduty_detector" "main" {
enable = true
datasources {
s3_logs { enable = true }
kubernetes { audit_logs { enable = true } }
}
}
resource "aws_cloudtrail" "main" {
name = "main-trail"
s3_bucket_name = aws_s3_bucket.cloudtrail.id
is_multi_region_trail = true
enable_log_file_validation = true
include_global_service_events = true
kms_key_id = aws_kms_key.cloudtrail_key.arn
}
This Terraform definition becomes audit evidence for CC7.2. Auditors can see the configuration was present throughout the observation period via git history and Terraform state.
CC8 — Change Management
CC8.1 requires that changes are authorized, tested, and reviewed before deployment. The combination of Terraform + pull request workflow satisfies this:
- Every infrastructure change requires a
terraform planreview in a pull request - Required reviewers in GitHub ensure changes are approved before merge
- Git history provides a timestamped, immutable record of every change
This is covered in the [SOC 2 change management for engineering guide](/blog/soc2-change-management-engineering-guide).
Policy as Code: Preventing Non-Compliant Infrastructure
Checkov
Checkov scans Terraform code against security policies that map to SOC 2 criteria. Run it in CI as a required check:
- name: Checkov Scan
uses: bridgecrewio/checkov-action@master
with:
directory: ./terraform
framework: terraform
check: CKV_AWS_53,CKV_AWS_54,CKV_AWS_18,CKV_AWS_19
When Checkov blocks a pull request, it creates an audit trail: the engineer had to remediate before the change was merged. That conversation is preserved in GitHub.
Sentinel (Terraform Cloud / Enterprise)
Sentinel policies enforce compliance at the Terraform platform level and cannot be bypassed by modifying the workflow:
import "tfplan/v2" as tfplans3_buckets = filter tfplan.resource_changes as _, rc {
rc.type is "aws_s3_bucket" and
rc.change.actions contains "create"
}
main = rule {
all s3_buckets as _, bucket {
bucket.change.after.server_side_encryption_configuration is not null
}
}
This policy fails any plan that creates an S3 bucket without server-side encryption configured.
Drift Detection and Evidence Generation
Even with IaC, drift can occur — a manual console change or an emergency fix applied directly. Detecting and documenting drift is itself a SOC 2 control.
Terraform state drift detection: Run terraform plan on a schedule (daily via CI/CD) and alert on unexpected changes. Any plan showing changes when no code change was made indicates drift.
AWS Config: Records the actual configuration state of every resource independently of Terraform state. [AWS Config and SOC 2](/blog/aws-config-soc2-cc71) covers this in detail.
SecureSpect: Continuously checks your live AWS configuration against SOC 2 controls, independent of your Terraform state. This catches both Terraform drift and resources never managed by Terraform.
Tagging Strategy for SOC 2 Evidence
AWS resource tags are machine-readable evidence that enables audit automation. Enforce required tags through Terraform locals:
locals {
required_tags = {
Environment = var.environment
DataClass = var.data_classification
Owner = var.team_name
SOC2InScope = "true"
}
}
resource "aws_instance" "app" {
tags = merge(local.required_tags, { Name = "app-server" })
}
During a SOC 2 audit, SOC2InScope = "true" tags allow you to filter AWS Config and CloudTrail events to only resources in scope — reducing the evidence set to exactly what the auditor needs.
Git History as Change Management Evidence
Every git commit to your Terraform repository is a timestamped, author-attributed record of an infrastructure change. For SOC 2 purposes:
- Commit messages should reference the ticket that authorized the change
- The pull request shows the reviewer and approval before merge
- The CI/CD log shows the Terraform apply result
Connecting IaC to Continuous Monitoring
Terraform defines your intended state. SecureSpect monitors your actual state. Together they provide a continuous compliance picture:
This workflow means your SOC 2 evidence is generated automatically, continuously, without manual collection effort. See [continuous compliance monitoring](/blog/continuous-compliance-monitoring) for more on this pattern.