compliance.tf

Getting Started with Operational Rules

Operational Rules apply your organization's operational standards — lifecycle blocks like prevent_destroy, tag ignore_changes, provisioner removal, instance type restrictions — to modules at download time, without forks or wrapper modules. They are independent from compliance controls: controls enforce regulatory requirements and run first, rules enforce operational standards and run second. See the Operational Rules overview for the full concept.

This guide takes you from viewing available rules to verifying they are applied. Typically under 5 minutes.

Prerequisites

Before you begin, make sure you have:

  • A compliance.tf account. Start a free trial or sign in.
  • Terraform >= 1.0 or OpenTofu >= 1.6 installed.
  • A module source pointing at a compliance.tf registry endpoint (e.g., soc2.compliance.tf/...). See Get Started if you have not set this up yet.
  • An access token configured via terraform login or .terraformrc for registry-form sources, and in .netrc for the HTTPS form used below. See Get Started and Registry Endpoints.

Step 1: Browse available rules

Browse the full catalog of available rules at Rule Catalog. Each rule page shows what the rule does, which resources it affects, and example before/after HCL.

The rules documented here are:

RuleWhat it does
lifecycle_prevent_destroy_dataAdds prevent_destroy = true to data-bearing resources (S3, RDS instances and Aurora clusters, DynamoDB, EFS, ElastiCache replication groups)
lifecycle_ignore_tagsAdds ignore_changes = [tags, tags_all] to all resources
lifecycle_ignore_autoscaling_changesAdds ignore_changes = [read_capacity, write_capacity] to DynamoDB tables
lifecycle_ignore_ami_changesAdds ignore_changes = [ami, image_id] to EC2 instances and launch templates
lifecycle_prevent_destroy_encryptionAdds prevent_destroy = true to KMS keys and Secrets Manager secrets
provisioner_remove_blocksRemoves all provisioner blocks from resources
resource_restrict_instance_typesDenies GPU and specialty instance types (p3, p4, x1, x2, u-*)
variable_allowed_regionsAdds a validation block to the module's region variable so a region outside your organization's allowlist is rejected
lifecycle_ignore_tag_keysAdds ignore_changes = [tags["<key>"]] for the tag keys your organization names; configurable from the organization's rules only, not from a URL

Step 2: Add rules to your module source

On a framework host (soc2.compliance.tf, registry.compliance.tf), the simplest way to apply rules is the ?rules= query parameter on your HTTPS module source URL. No API calls needed. Just add the rules you want directly in your Terraform configuration:

module "s3_bucket" {
  source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=lifecycle_prevent_destroy_data,lifecycle_ignore_tags,provisioner_remove_blocks"
}

This works on every plan and gives you per-module control over which rules are applied. Rule ids are flat snake_case slugs (lifecycle_prevent_destroy_data) — the same shape as control ids, with no namespace prefix. A name in ?rules= that does not match ^-?[a-z][a-z0-9_]&#123;1,63&#125;$ is refused with a 400. ?add_rules= takes bare ids only, so its pattern has no sign at all - ^[a-z][a-z0-9_]&#123;1,63&#125;$ - and ?add_rules=-<id> is a 400 naming ?rules=-<id>: subtraction keeps exactly one spelling. ?rules= carries names only: each rule runs with its catalog defaults, so a rule whose defaults are empty (Allowed Regions) does nothing from a URL. When your organization has a Baseline (below), an unprefixed list replaces it for that download; ?rules=-lifecycle_prevent_destroy_data removes a rule from it, and ?add_rules=lifecycle_prevent_destroy_data adds one on top of it.

A ?rules= list must be all bare or all - prefixed — mixing the two is a 400 rather than a silent replacement. A bare ?rules= list cannot be combined with ?add_rules= either: a bare list replaces your Baseline, so nothing is left to add to. ?rules=-<id> together with ?add_rules=<id> is fine, because both work against the Baseline.

The + add prefix was removed. It reached the registry only when written %2B, and a literal + in a query string decodes to a space — so ?rules=+some_rule arrived as a bare name with a leading space, one step away from silently replacing your Baseline. Any + in ?rules= or ?add_rules=, in either spelling, is now a 400 that names ?add_rules=.

To see what a ?rules= list does before you commit it, run it in the Rules Playground. It applies the same rules engine to a reviewed set of upstream modules and shows the real diff — no account, and no terraform init.

On your organization's host (<alias>.compliance.tf), rules come from the organization's configuration, and ?rules= and ?add_rules= — like ?enable=, ?disable= and ?framework= — are refused with a 400. All five author posture from a query string, so all five are refused there; ?ruleset= is the one parameter an organization host does accept, because it names a group the organization itself published rather than whatever the caller typed. Org admins on a trial, paid or enterprise plan configure them from the Operational Rules page of the dashboard at app.compliance.tf (backed by PUT /api/org/rules). That needs a signed-in session with the org admin role — separate from the ctf_ registry access token, which authenticates module downloads only. The page holds two kinds of group: the Baseline applies to every module downloaded from the organization's host, and a named ruleset applies to the modules that select it with ?ruleset=<name>:

module "logs_bucket" {
  source = "https://acme.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&ruleset=ignore-scanner-tags"
}

A ruleset adds to the Baseline; it never removes from it. Parameters — the tag keys to ignore, the regions to allow — are set per rule on that page and never travel in a URL. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead. Baseline rules and rulesets has the full model, including which credential the HTTPS form needs.


Step 3: Verify rules are applied

Run terraform init -upgrade on any module sourced from the compliance.tf registry (-upgrade forces a fresh download past the local module cache):

terraform init -upgrade

Expected output:

Initializing modules...
Downloading https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=lifecycle_prevent_destroy_data,lifecycle_ignore_tags,provisioner_remove_blocks for s3_bucket...
- s3_bucket in .terraform/modules/s3_bucket

Initializing provider plugins...

Terraform has been successfully initialized!

After init completes, inspect the downloaded module to confirm rules were applied. For example, if you enabled Prevent Destroy Data, check the S3 bucket resource:

grep -B1 -A2 "prevent_destroy" .terraform/modules/s3_bucket/main.tf

Expected output:

  lifecycle {
    prevent_destroy = true
  }

The lifecycle block is now in the module source code. It was not there in the upstream terraform-aws-modules version.


Rules manifest

A compliancetf-manifest.json file is written into any module compliance.tf builds for you, recording the rules that were requested, the file paths that changed, and a variant fingerprint. terraform init writes it into the module directory under .terraform/modules/, so you can check it from your own checkout rather than from a response you did not keep. Read each rule's outcome rather than its presence: a rule can be listed and still have enforced nothing. See operational rules for how to read it as evidence.


Troubleshooting

On a framework host, check that your module source URL includes the ?rules= parameter with the rules you want. On your organization's host, check the Operational Rules page: the rule must be in the Baseline, or in the ruleset the module selects, and the rules state must be Enforced.

If neither is set, and your organization has no rules frozen in its baseline snapshot, no rules are applied. This issue can also occur when the module was cached from a previous download. Run terraform init -upgrade to force a fresh download.

Controls run first, rules run second. If both affect the same attribute, changes are merged additively (e.g., both adding entries to ignore_changes). Explicit conflict detection, where a rule contradicts a control, is planned for a future release. If you see unexpected behavior, adjust your rule configuration to avoid overlap with the control.

On a framework host, use the per-request override in the module source URL:

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

The - prefix removes a rule for this specific download. To add a rule that is not in your organization's defaults, use the ?add_rules= parameter instead: ?add_rules=lifecycle_ignore_ami_changes.

On your organization's host ?rules= and ?add_rules= are both refused with a 400. A Baseline rule applies to every module, so move it into a ruleset that only the modules needing it select, or disable it for the whole organization. See Baseline rules and rulesets.

The org rules API uses your signed-in dashboard session, not the registry access token. Sign in at app.compliance.tf and retry; a 403 means your account lacks the org admin role or your organization is not on a trial, paid or enterprise plan. (The ctf_ access token from the Access Tokens page authenticates module downloads, not this API.)


Next steps

On this page

Ask AI about this

Help improve this page