SOC 2 Terraform Compliance: IaC Security Controls
SOC 2 Terraform compliance guide covering IaC security scanning, state file security, module version pinning, drift detection, and change management evidence for CC8.1.
- Terraform as the single source of truth for infrastructure provides continuous CC8.1 change management evidence.
- tfsec, Checkov, and Terrascan scan Terraform code for security misconfigurations before they are applied.
- Terraform state files contain sensitive resource attributes — store in S3 with encryption, versioning, and state locking.
- A policy-as-code layer (Sentinel or OPA Conftest) blocks non-compliant configurations from being applied.
- Drift detection via `terraform plan` scheduled runs reveals unauthorized out-of-band changes.
- Terraform Cloud/Enterprise run history provides an immutable audit trail of every infrastructure change.
In this guide
Why IaC Matters for SOC 2 CC8.1
CC8.1 requires that changes to the system are authorized, tested, and deployed in a controlled manner. Infrastructure-as-Code with Terraform satisfies this criterion better than any manual process. Every infrastructure change is a Git commit with an author, a timestamp, a PR with reviewer approval, a CI pipeline with automated testing, and a deployment log. This paper trail is exactly what auditors want to see.
The alternative — making infrastructure changes via the AWS console, Azure portal, or GCP console — leaves no structured audit trail. Even with CloudTrail enabled, reconstructing a timeline of intentional changes vs. accidental clicks vs. automated changes is laborious. Terraform eliminates ambiguity: if a resource exists in the Terraform code and was applied through the pipeline, it was intentional and authorized.
Terraform State File Security
The Terraform state file (`terraform.tfstate`) contains sensitive information: resource IDs, IP addresses, and in some cases plaintext values of sensitive resource attributes (database passwords, initial API keys). Never commit state files to Git. Configure a remote backend using S3 + DynamoDB for state locking: in `main.tf`, define `terraform { backend "s3" { bucket = "company-tf-state" key = "prod/terraform.tfstate" region = "us-east-1" encrypt = true kms_key_id = "arn:aws:kms:..." dynamodb_table = "terraform-lock" } }`.
Enable versioning on the S3 state bucket so that previous state versions are retained. Set an S3 lifecycle policy to retain versions for 365 days. Enable S3 Object Lock in governance mode to prevent accidental deletion of state history. Grant access to the state bucket only to the CI/CD pipeline role and designated platform engineers — not to the entire engineering team. Rotate the KMS key annually.
Consider migrating to Terraform Cloud (HCP Terraform) for managed state with built-in encryption, access controls, and run history. Terraform Cloud state is encrypted at rest and in transit, access is controlled by workspace permissions, and state history is retained indefinitely with downloadable artifacts. The run history in Terraform Cloud serves as an immutable audit log for CC8.1 evidence.
Static Security Scanning (tfsec, Checkov)
Run static analysis on Terraform code in every PR to catch security misconfigurations before they reach production. Three leading tools: (1) tfsec (`brew install tfsec` or `docker run aquasec/tfsec .`) — scans for 200+ checks across AWS, Azure, GCP, and Kubernetes. (2) Checkov (`pip install checkov` or `checkov -d .`) — 1000+ checks with SARIF output for GitHub Code Scanning integration. (3) Terrascan — 500+ policies with OPA Rego backend.
In GitHub Actions, add this step to your PR workflow: `- name: tfsec / run: tfsec --no-color --format sarif --out results.sarif . / - uses: github/codeql-action/upload-sarif@v3 / with: sarif_file: results.sarif`. This uploads tfsec findings to GitHub Code Scanning, where they appear as PR annotations and in the Security tab. Block merges on HIGH severity findings using a required status check.
Configure tfsec to ignore specific checks with `.tfsec/config.yml` or inline `#tfsec:ignore:AWS017` comments. Whenever you add an ignore, require a comment explaining the business justification. This creates a documented exception trail that auditors can verify — every suppressed finding has a reason, which is far better than either unfixed findings or unexplained suppressions.
Policy-as-Code with Sentinel or OPA
Static scanning catches misconfigurations in code. Policy-as-code goes further by blocking non-compliant plans from being applied, even if the code looks syntactically correct. HashiCorp Sentinel (available in Terraform Cloud Plus) and OPA Conftest (open source) both work for this. A Sentinel policy example: `import "tfplan/v2"; rule "no_public_s3" { all tfplan.resource_changes as _, changes { changes.after.acl not in ["public-read", "public-read-write"] } }`.
Key policies to implement for SOC 2: block S3 buckets with public ACLs, block security groups with 0.0.0.0/0 ingress on sensitive ports, require encryption on all RDS instances and EBS volumes, require tags on all resources (owner, environment, data-classification), and block use of the default VPC. These policies, checked on every `terraform apply`, ensure that the security baseline established in your AWS security policy is continuously enforced.
With OPA Conftest, write policies in Rego: `deny[msg] { input.planned_values.root_module.resources[_].type == "aws_s3_bucket" input.planned_values.root_module.resources[_].values.acl == "public-read" msg := "S3 bucket with public-read ACL is not allowed" }`. Run in CI: `terraform show -json tfplan.binary | conftest test -`. Failed checks block the pipeline and require a policy exception approved by the security team.
Change Management Workflow
Define a documented Terraform change management workflow. For production changes: (1) Engineer creates a feature branch and opens a PR. (2) CI runs `terraform plan`, tfsec, Checkov, and Conftest. (3) PR requires approval from two platform engineers (or one for low-risk changes). (4) After merge to main, CI runs `terraform apply` automatically for non-destructive changes, or requires a manual approval step for destructive operations (`terraform destroy`, database migrations). (5) The apply log is stored as a CI artifact with the PR number and commit SHA for traceability.
Document this workflow in your change management policy. Link each production Terraform apply to the PR that authorized it. In Terraform Cloud, this is automatic — every apply shows the VCS run trigger and commit. For self-hosted pipelines using GitHub Actions, use the GitHub Environments feature with a required reviewer for production applies, and ensure the workflow prints the PR URL in the apply log for cross-referencing.
Drift Detection and Out-of-Band Changes
Infrastructure drift occurs when someone makes a change outside the Terraform pipeline (e.g., manually modifying a security group in the AWS console). Drift is both a security risk and a SOC 2 evidence problem — if your infrastructure does not match your code, the code-based evidence is inaccurate. Detect drift by running `terraform plan` on a schedule and alerting when there are unexpected diffs.
Set up a GitHub Actions scheduled workflow (`on: schedule: - cron: "0 6 * * *"`) that runs `terraform plan -detailed-exitcode` and sends a Slack alert if the exit code is 2 (diff detected). Include the plan output in the alert. This provides a daily check that production infrastructure matches the declared Terraform state. For Terraform Cloud, use the Drift Detection feature in HCP Terraform Plus which does this automatically.
When drift is detected, triage immediately: was it an authorized emergency change (which should be back-ported to Terraform within 24 hours) or an unauthorized change (which is a security incident requiring CC7.3 response)? Document the triage result and remediation. This process + the drift detection alerts constitute evidence of ongoing CC8.1 change management oversight.
Module Version Pinning and Supply Chain
Terraform modules from the public registry can be vectors for supply chain attacks if not version-pinned. Always specify a version constraint in module sources: `module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "= 5.8.1" }`. The `=` constraint pins to an exact version; `~> 5.8` allows patch updates. For modules from Git sources, pin to a specific tag: `source = "git::https://github.com/org/module.git?ref=v2.1.0"`.
Maintain an internal module registry for modules used across multiple teams. Internal modules can be reviewed, security-scanned, and version-controlled under your change management process. When using external public modules, review the source code before pinning a version — check for suspicious `provisioner "local-exec"` blocks or unexpected IAM permissions. Add external module reviews to your vendor risk management process.
Evidence Collection for CC8.1
For each audit period, compile the following Terraform-related evidence for CC8.1: (1) Terraform plan and apply logs for all production changes, showing the PR number, commit SHA, approver(s), and timestamp. Export from Terraform Cloud or your CI system. (2) tfsec/Checkov scan results for each PR merged during the audit period — show that scanning was required and findings were addressed. (3) Policy-as-code enforcement logs showing blocked applies (evidence that controls were tested). (4) Drift detection reports showing either no drift or documented investigation of detected drift.
Organize this evidence by date range and control. Label each piece of evidence with the SOC 2 criterion it satisfies: apply logs → CC8.1, scanning results → CC7.1, access to state bucket → CC6.1. This structured evidence package allows auditors to efficiently verify controls without extensive back-and-forth and signals that your team understands what they are doing, not just going through compliance motions.
Frequently Asked Questions
Does using Terraform automatically satisfy CC8.1 for SOC 2?
Should we use Terraform Cloud or HCP Terraform for SOC 2?
How do we handle Terraform apply in an emergency (hotfix) for SOC 2?
What sensitive data is in a Terraform state file?
Can we use Pulumi or CDK instead of Terraform for SOC 2?
Automate your compliance today
AuditPath runs 86+ automated checks across AWS, GitHub, Okta, and 14 more integrations. SOC 2 and DPDP Act. Free plan available.
Start for free