Sign in
Menu
Sign up
Docs
Use the modules

Turn controls on and off

For an engineer whose module needs a different set of controls from the framework default, and for the reviewer who approves that change. When you finish, the module source carries the exception in plain sight, where the pull request and the auditor can read it.

  • Controls cover encryption at rest, access controls such as public access blocks and IMDSv2, logging and monitoring, data lifecycle, and network security. The controls catalog lists them all.
  • Each framework's registry host serves that framework's set of controls; ?disable= removes some and ?enable= adds some, per module source.
  • The parameters work only in the HTTPS URL format, and not on your organization's own host.
  • An exception is a line in your Terraform code, so it goes through the same review as any other change.

When to turn a control off

Turn a control off when it does not fit this one resource and you can say why in a sentence: a short-lived dev bucket that should not carry Object Lock for good, or an S3 event notification nothing in the account consumes. Fix the configuration instead when the control fits and the plan fails because a value is missing. Do not turn off an encryption, public-access or logging control on a resource in audit scope: the auditor will ask why it is absent.

To add a control, use ?enable= with a control your framework does not map, for example a stricter key requirement on top of CIS AWS v6.0.

Syntax

module "..." {
  source = "https://<framework>.compliance.tf/terraform-aws-modules/<module>/aws?version=<version>&disable=<id>,<id>&enable=<id>"
}
  • disable and enable each take a comma-separated list of control IDs, such as s3_bucket_object_lock_enabled. The ID is the last part of the control's URL in the controls catalog.
  • Either parameter can appear alone. version is optional but should be pinned.
  • registry.compliance.tf serves no controls. There, ?enable= builds a set from nothing.

The source format, its authentication through .netrc and its other parameters are on Registry endpoints.

What enable and disable can and cannot do

How the final control set is resolved Flowchart: framework controls and explicitly enabled controls are combined into the active set, disabled controls are removed, and the final control set drives Terraform module generation. Framework controlse.g. SOC 2, HIPAA Enabled controls?enable= parameter All active controlsunion Remove disabled?disable= parameter Final control set Generate moduleTerraform source LegendRequest inputsResolution stepResultOutput

The registry starts from the framework's set of controls, removes the disable list, adds the enable list, and builds the module with the result.

  • Only a control the module enforces can be turned off. A framework maps more controls than a module enforces: the mapped set describes the framework, the enforced set is what the module applies. Disabling a mapped-only control does nothing, and the registry does not tell you so. The same goes for enabling a control the module does not implement. The module page lists which controls a module enforces.
  • The Registry format takes no parameters. soc2.compliance.tf/... always serves the framework's full set. Switch the module to the HTTPS form to change it; that form authenticates from .netrc, so a runner such as HCP Terraform needs one. See CI/CD integration.
  • Your organization's host refuses them. On <alias>.compliance.tf a ?disable= or ?enable= returns 400: the binding on the environment decides the posture. Exceptions there are waivers, approved in the console; see Promotion and gates.
  • The source cannot be a variable. Terraform requires a literal source, so each environment that needs a different set needs its own source line. See One source per environment.
  • Turning a control off does not undo it. Values already applied, such as a logging configuration, stay on the resource. Compliance.tf stops checking them for this module.

Examples

Turn off one control

SOC 2 maps s3_bucket_object_lock_enabled. A bucket that does not need Object Lock turns it off:

module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_object_lock_enabled"

  bucket = "my-app-scratch"

  logging = {
    target_bucket = module.logging_bucket.s3_bucket_id
    target_prefix = "s3-access-logs/my-app-scratch/"
  }

  # Plus the KMS key, lifecycle rule and replication rule SOC 2 also needs; see the S3 bucket playbook
}

Add a control the framework does not map

CIS AWS v6.0 does not map s3_bucket_default_encryption_enabled_kms. Adding it makes the plan require a KMS key you manage:

module "s3_bucket" {
  source = "https://cis.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&enable=s3_bucket_default_encryption_enabled_kms"

  bucket = "my-app-data"

  server_side_encryption_configuration = {
    rule = {
      apply_server_side_encryption_by_default = {
        sse_algorithm     = "aws:kms"
        kms_master_key_id = module.kms_key.key_arn
      }
    }
  }
}

SOC 2, PCI DSS v4.0 and HIPAA already map this control, so on their hosts the module requires the key without ?enable=.

Start from no controls

module "s3_bucket" {
  source = "https://registry.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&enable=s3_bucket_versioning_enabled,s3_bucket_object_lock_enabled"
}

One source per environment

Terraform does not accept a variable or a local in a module source, in either format. Give each environment root its own literal source line instead:

module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_object_lock_enabled"

  bucket = "my-app-data-dev"

  logging = {
    target_bucket = module.logging_bucket.s3_bucket_id
    target_prefix = "s3-access-logs/my-app-dev/"
  }

  # Plus the KMS key, lifecycle rule and replication rule SOC 2 also needs
}
module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0"

  bucket = "my-app-data-prod"

  logging = {
    target_bucket = module.logging_bucket.s3_bucket_id
    target_prefix = "s3-access-logs/my-app-prod/"
  }

  # Plus the KMS key, lifecycle rule and replication rule SOC 2 also needs
}

The settings each SOC 2 S3 control needs are in the S3 bucket playbook. Production keeps every SOC 2 control. Dev turns off Object Lock, because AWS never lets a bucket turn Object Lock off once it is on, which matters for buckets you create and destroy often.

Terraform workspaces share one configuration, so they cannot differ in source; use separate roots. OpenTofu 1.8 and later evaluates variables in source early, but a literal per root keeps the exception visible in each environment's code.

Record the exception in review

A ?disable= is a change to what is enforced, so review it like one:

  • Put the reason in the pull request description, or in a comment next to the source line.
  • Keep one control per reason: a list of five IDs with one justification hides four decisions.
  • Check the framework page for the clauses the control maps to, so the reviewer knows what is being given up.

The source line is in version control, so the exception and its history are part of the evidence; see Audit evidence.

On this page

Ask AI about this

Help improve this page