At AWS re:Invent? Bring your audit questions to Anton Babenko.Meet Anton at AWS re:InventAsk for a time
Sign in
Menu
Start free
Make Non-Compliant Terraform Impossible With Compliance.tf

Make Non-Compliant Terraform Impossible With Compliance.tf

In this post
  1. A Different Approach: Compliance at the Registry Level
  2. How Controls Are Enforced Inside the Modules
  3. Why Bypass Attempts Fail
  4. DIY Scanning vs Built-in Prevention
  5. Next Steps
In this post
  1. A Different Approach: Compliance at the Registry Level
  2. How Controls Are Enforced Inside the Modules
  3. Why Bypass Attempts Fail
  4. DIY Scanning vs Built-in Prevention
  5. Next Steps

See how to verify these guardrails in practice

The second article in this series shows how IaC scanners, infrastructure scanners, and audit evidence fit around compliance.tf modules after the preventive controls are in place.

Read the verification guide

You've been working on the same S3 module configuration for an hour. CI blocked the pull request: Checkov reports 14 findings. Three are real. The rest are policy noise from rules written for a different service. The sprint review is tomorrow.

This is how compliance debt accumulates. Write code, scan it, negotiate findings, defer what you can, fix what you must. Compliance becomes a column in the backlog rather than a property of the infrastructure.

The alternative: build compliance controls into the modules themselves. When the module blocks non-compliant values at plan time, there's no finding to debate and no scanner policy to keep aligned with what the module actually creates.

A Different Approach: Compliance at the Registry Level

Compliance.tf is a private Terraform registry that serves pre-curated versions of terraform-aws-modules with compliance constraints built into the module code. The interface is identical to the original modules. The difference is what happens when you pass a non-compliant value.

Here is what that looks like:

# Before: Using official Terraform registry
module "s3_bucket" {
  source  = "terraform-aws-modules/s3-bucket/aws"
  version = "5.9.0"
  bucket  = "my-data"
}

# After: Using compliance.tf registry with SOC 2 (CC6.1, CC7.2) controls
module "s3_bucket" {
  source  = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
  version = "5.9.0"
  bucket  = "my-data"
}

Only the source URL changes. The module interface — every variable name, every output — stays the same. The registry namespace (soc2.compliance.tf, pcidss.compliance.tf, hipaa.compliance.tf) selects which control set applies to that module.

Under the hood:

For the modules compliance.tf supports, there are no wrapper modules to build, no policy-as-code rules to maintain, and no scanner policies to keep aligned with upstream framework changes. The compliance layer travels with the module.

How Controls Are Enforced Inside the Modules

Compliance.tf applies constraints inside the module code, before resources are provisioned. Controls map directly to framework requirements.

Enforcement happens through three mechanisms:

  • Safe defaults. Options like encryption, logging, public access blocks, and secure transport are on by default and set to compliant values. Callers do not need to configure them to get compliant behavior.
  • Required inputs with validators. Certain variables must be provided and must satisfy validation rules. A non-compliant value causes terraform plan to fail with a clear error.
  • Restricted configuration surface. Some dangerous options are not exposed at all. Others are accessible only through explicit exception paths.

For example, a TLS version constraint looks like this inside the module:

variable "minimum_tls_version" {
  description = "Minimum version of TLS"
  type        = string
  default     = "TLS1_2"

  validation {
    condition     = contains(["TLS1_2", "TLS1_3"], var.minimum_tls_version)
    error_message = "TLS version must meet compliance requirements (TLS1_2 or higher)."
  }
}

When terraform plan runs, Terraform evaluates validation rules before generating the plan. A non-compliant value fails immediately. The configuration never enters a plan file, so there is no finding for a scanner to report — for the controls this module enforces.

Why Bypass Attempts Fail

Take S3 bucket logging. It is required by SOC 2 (CC7.2), PCI DSS v4.0 (10.2.1), and CIS Controls v8.0 IG1 (8.2). With a standard module, a developer might disable it by omitting the block or passing an empty value:

# Attempting to skip logging configuration
module "s3_bucket" {
  source  = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
  version = "5.9.0"

  bucket = "my-data"

  # Trying to disable logging - this will not work
  logging = {}
}

With compliance.tf, this fails. The S3 bucket logging control is enforced inside the module. The constraint is structural: the module ensures logging is configured regardless of what the caller passes.

The error is explicit:

Control 's3_bucket_logging_enabled': `logging.target_bucket` must be set to enable S3 bucket access logging.
Read more: https://compliance.tf/docs/controls/aws/s3_bucket_logging_enabled/

Where exceptions are needed, they require explicit flags or separate modules. Any deviation is visible to security teams and auditors. Learn how to customize controls when your situation requires it.

DIY Scanning vs Built-in Prevention

Both IaC scanners and compliance.tf modules aim for compliant infrastructure. They reach it differently.

Two paths to a compliant apply Flowchart comparing two routes from writing Terraform to a compliant apply: the detection route loops through a CI scanner, findings triage, and re-planning, while the prevention route uses a compliance.tf module whose built-in controls fail the first plan with a named fix, reaching apply without a triage loop. Detect after the fact Prevent in the module Plan JSON Findings Fix + re-plan Clean, later First plan Developerwrites Terraform Upstream moduleany value allowed terraform planno controls checked Scanner in CI14 findings · 3 real Triage and fixnegotiate the noise Compliance.tf modulecontrols inside · soc2. terraform planerror names the control terraform applycompliant deployment LegendAuthorStepDetective layer (CI)Preventive layerResult
DIY approachCompliance.tf
When controls are appliedAfter code is written (scanner finds it)Before plan is accepted (module blocks it)
Who maintains rulesYour teamcompliance.tf team
Framework updatesManual effort per frameworkManaged upstream
Coverage scopeWhatever you write policies forSupported modules and controls

The shift is from detection to prevention. When a control lives inside the module, there is no gap between the policy and what the module creates. Your scanners — Checkov, Trivy, Prowler — keep running. They provide independent verification that the guardrails work.

For the enterprise context behind this approach — including an honest comparison with using terraform-aws-modules directly — see Why Enterprises Choose Compliance.tf Over terraform-aws-modules.

Next Steps

Compliance.tf modules enforce controls at the point of creation. Your existing scanners keep running alongside and provide independent confirmation. That gives you prevention where the module covers it, and detection for everything else.