AWS SecurityTerraformIaCSOC 2Policy as CodeAWSCompliance Automation

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.

SecureSpect Team··13 min read

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 plan review 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 tfplan

s3_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
Preserve git history permanently. Auditors reviewing CC8 controls will sample specific changes and trace them from ticket to PR to apply.

Connecting IaC to Continuous Monitoring

Terraform defines your intended state. SecureSpect monitors your actual state. Together they provide a continuous compliance picture:

  • Engineer defines a new S3 bucket in Terraform with required encryption and tagging
  • Checkov validates the configuration in CI before merge
  • Terraform applies the resource to AWS
  • SecureSpect scans the bucket within minutes of creation and confirms controls are active
  • SecureSpect continues checking daily and alerts on configuration changes
  • 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.

    Automate your SOC 2 evidence collection

    Connect your AWS and GitHub environments and start collecting audit-ready evidence today. Free to start.

    Start Free →More Articles