Ignore Selected Tag Keys
Adds ignore_changes = [tags["<key>"]] for each tag key your organization configures. Drift on those keys is ignored; every other tag stays managed by Terraform.
When to use this rule
Use this when: One or two external systems, such as a security scanner or an inventory agent, write their own tag keys onto resources, and you want Terraform to keep managing every other tag.
Do not use this when: Tags drift on many keys from many sources; Ignore Tag Changes ignores tags and tags_all wholesale. Or you rely on tag drift showing up in terraform plan as a detection signal.
Why this rule exists
Some tags are owned by a system other than Terraform: a vulnerability scanner records when it last inspected a bucket, an inventory agent stamps an asset id, a cost tool writes an allocation code. Terraform sees each of those as drift and plans to remove the tag on the next run.
Ignore Tag Changes answers that by ignoring tags and tags_all wholesale, which also silences every tag Terraform is supposed to manage. This rule ignores only the keys you name. The keys travel as a parameter of the organization's configuration, never in a module source URL, and the registry writes them into HCL as literal map keys, so a key cannot carry anything but a tag key.
Related Terraform and OpenTofu issues
| Repository | Issue | Title |
|---|---|---|
| hashicorp/terraform | #27360 | A method to override configuration and meta arguments within a module |
| hashicorp/terraform | #24188 | Support for dynamic blocks and meta-arguments |
Affected resources
| Resource | Service | Why |
|---|---|---|
aws_s3_bucket | Amazon S3 | The default target. Add other taggable types through resource_types. |
What this rule does
Adds a lifecycle { ignore_changes = [tags["<key>"]] } entry to each matching resource, one entry per key in tag_keys. Terraform and OpenTofu then ignore external changes to those keys and keep managing every other tag. A resource that already carries an ignore_changes list keeps it; the keys are merged in.
Ignored keys: the tag_keys your organization configures. The shipped default is empty, so the rule does nothing until it is configured.
Before and after
Before (upstream module):
resource "aws_s3_bucket" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Ignore Selected Tag Keys applied, tag_keys = ["scanner-last-seen"]):
resource "aws_s3_bucket" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
ignore_changes = [tags["scanner-last-seen"]]
}
}The key list is whatever your organization configured; the one above is an example.
The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.
Real-world scenario
A vulnerability scanner wrote a scanner-last-seen tag on every bucket it inspected. Each terraform plan proposed removing it from 30 buckets. Ignoring all tags would have hidden a cost-allocation tag the finance team relied on, so the platform team ignored the one key instead.
Known limits
- Only the keys listed in
tag_keysare ignored. Every other key stays managed, and the value Terraform sets at creation time is never changed. - Applies to the resource types in
resource_types(defaultaws_s3_bucket), matched on the type name. Other taggable types must be added explicitly. - Cannot be selected from a module source URL.
?rules=accepts rule names without a dot, and the rule's defaulttag_keysis empty, so it only does something through your organization's rule configuration. - A key outside the AWS tag-key character set is rejected when the rule is saved. A value the registry cannot encode is dropped, and the rule falls back to its empty default, rather than written into HCL.
- Only
.tfand.tofufiles are read. Modules written in JSON syntax (.tf.json,.tofu.json) are left untouched. - Only the module's own directory and its first-level
modules/*submodules are processed. Modules that this module calls by source URL are separate downloads and are handled on their own request.
Compliance framework support
This rule is not a compliance control. It supports these framework objectives as an operational safeguard:
| Framework | Controls | Role |
|---|---|---|
| SOC 2 | CC6.1 | Keeps the change-management signal intact: only the externally owned keys are silenced, so every other tag change still shows in the plan |
Default configuration
These are the values the rule ships with. They are not settable from a module source URL: ?rules= carries rule names only, so a per-request enable uses exactly the defaults below. Your organization sets them per rule, in its Baseline or in a ruleset, from the Operational Rules page; see Baseline rules and rulesets.
This rule does nothing until your organization configures it
Its shipped defaults enforce nothing: the rule acts only on the values your organization configures for it. The build still runs and still writes a manifest, and the rule's entry there says it enforced nothing. Configure it in the Baseline or in a ruleset; it is not selectable from a module source URL.
| Parameter | Type | Default | Description |
|---|---|---|---|
tag_keys | list(string) | [] | AWS tag keys to ignore drift on. Each is emitted as ignore_changes = [tags["<key>"]] with safe literal encoding; empty = unconfigured no-op |
resource_types | list(string) | ["aws_s3_bucket"] | Target resource type patterns. A module with none of these is not applicable |
How to enable
On a framework host, per request:
Add ?rules=lifecycle_ignore_tag_keys to your HTTPS module source. Use the full namespaced id. A bare lifecycle_ignore_tag_keys matches no rule: it is skipped server-side and your terraform init still succeeds, without the rule.
module "example" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=lifecycle_ignore_tag_keys"
}On your organization's host:
Add the rule to the organization's Baseline, which applies to every module served from the bare organization form <alias>.compliance.tf/<namespace>/<name>/<provider>, or to a named ruleset that one module selects with ?ruleset=<name> on the HTTPS form of that address. A project environment (<alias>.compliance.tf/<project>/<env>/<module>) serves the rules frozen in its bound snapshot instead: there the rule goes into the config draft and reaches the environment by promotion. On both organization forms ?rules= is refused with a 400 — including the subtractive - prefix — rather than dropped, so you cannot mistake an ignored override for an applied one. Change the configuration, not the URL. See Baseline rules and rulesets for the configuration flow, and registry resolution for how the address forms differ.
How to check it worked
Read compliancetf-manifest.json in the module you received. The rule's entry carries an outcome: enforced means an ignore_changes entry was written; not_applicable means nothing was written, either because no resource of a targeted type was present or because tag_keys was empty. When the rule reached the module through a ruleset, the entry also carries ruleset.name and ruleset.content_hash. Do not infer enforcement from a successful terraform init; a rule that enforces nothing also inits cleanly.
Failure modes
| Scenario | Result |
|---|---|
Rule enabled with an empty tag_keys | No-op. Nothing is added, and the manifest records the rule as not_applicable, the same outcome as a module with no matching resource. Tell the two apart from the configuration, not the manifest. |
| Module has no resource of a configured type | No-op. Module delivered unchanged; the manifest marks the rule not_applicable. |
The resource already has a lifecycle { ignore_changes = [...] } list | The key is merged into the existing list. Entries already there are kept. |
| You need Terraform to manage that key again | Remove the key from tag_keys, or move the rule from the Baseline into a ruleset that only the affected modules select. On your organization's host a URL parameter cannot remove it: ?rules= is refused with a 400. |
Terraform and OpenTofu compatible
This rule works with both Terraform (1.x+) and OpenTofu (1.6+). It reads ordinary HCL, and anything it changes stays ordinary HCL, so the module behaves the same under either tool.
Help us improve this page
Operational Rules are a new feature. We'd love your feedback on this rule page — what's useful, what's missing, what's confusing. Share feedback.