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>"
}disableandenableeach take a comma-separated list of control IDs, such ass3_bucket_object_lock_enabled. The ID is the last part of the control's URL in the controls catalog.- Either parameter can appear alone.
versionis optional but should be pinned. registry.compliance.tfserves 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
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.tfa?disable=or?enable=returns400: 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.