Operational Rule Definitions
Which rules run is decided before your source string gets a say. An
organization host applies what that organization's admins configured. A
framework host applies your own organization's Baseline when its rules state is
Enforced, and ?rules= then replaces or subtracts from that set for a single
download, while ?add_rules= adds to it. Either way, picking a rule starts with knowing that it exists
and what it would do.
This page is that list. Every selectable rule appears below with its id, its
parameters, its shipped defaults, and the transformers it runs, in the HCL the
engine loads. Read the one you are considering before you put its id in a
source line.
Each rule has its own anchor, so a rule can be linked to directly.
eks_node_group_set_desired_size is /rules/definitions/#eks_node_group_set_desired_size.
What this page is, and what it is not
It is the list of rules you can name, with what each one would change. The definitions are rendered from the rule catalog that ships with the engine, so a change in the engine changes this page.
It is not a reusable artifact. There is no licence attached to these definitions and no grant of any kind is made. They are published so you can choose a rule, not so they can be lifted into another tool.
It is not the engine's source files byte-for-byte. See How this page is rendered.
It is not a record of what ran on a download. A module compliance.tf builds
for you carries a compliancetf-manifest.json naming every rule the build
received, each with an outcome saying what it actually did — a listed rule
may have enforced nothing. That is the per-download record; this is the menu.
Rules are operational, not regulatory — see Operational Rules for how the two layers differ.
How this page is rendered
The definitions come from the rule catalog the engine publishes
(rules-catalog.json, engine version 0.27.0), which is the
engine's own serialization of its rule files. This site renders a vendored
snapshot of that catalog rather than reading it live, so the version above is
the snapshot's own: when it lags the engine the registry is running, a
definition on this page lags with it.
Three things follow from rendering the catalog rather than copying the source files, and they matter when you are reading a definition to decide on it:
- The
intentblock is not shown. It selects which blocks a rule inspects. The catalog stores a derived, lossy form of it, and reconstructing one would mean printing HCL that a rule does not have. - A field left at an empty or wildcard default is absent from the transformer
block. The catalog drops those. The parameter table in each definition is
still complete, so a parameter you see with a
[]default is real even when no transformer line mentions it. - A value that equals a parameter's default is shown as
param.<name>. That is how the rules are written, and it keeps a configurable value from reading as a hard-coded one.
The per-rule pages carry the parts a definition cannot show you: before-and-after HCL, known limits, failure modes, and the break-glass path.
Naming a rule
Append the rule id to the module source, exactly as it is written below. Nothing else about the source line changes.
module "example" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&rules=eks_node_group_set_desired_size"
}Parameters never travel in the URL, so a rule named this way runs with the defaults printed below. Changing a parameter means configuring the rule in an organization Baseline or ruleset — see Baseline rules and rulesets.
eks_node_group_set_desired_size
Let Terraform change a terraform-aws-eks managed node group's desired, min and max size after create, with the AWS CLI from terraform_data (upstream ignores desired_size). Opt-in: needs bash, the AWS CLI and credentials for the node group's account where terraform apply runs; the first apply pushes desired_size once, so set it to the live value first
| Severity | warning |
| Transformers | lifecycle (safe), file_create (danger) |
| Family | provisioner_remove_blocks — at most one rule in a family applies to a given resource type |
| Page | Set EKS Node Group Desired Size |
rule "eks_node_group_set_desired_size" {
description = "Let Terraform change a terraform-aws-eks managed node group's desired, min and max size after create, with the AWS CLI from terraform_data (upstream ignores desired_size). Opt-in: needs bash, the AWS CLI and credentials for the node group's account where terraform apply runs; the first apply pushes desired_size once, so set it to the live value first"
severity = "warning"
family = "provisioner_remove_blocks"
ack_risk = true
transformer "lifecycle" {
action = "add"
ignore_changes = ["scaling_config[0].min_size", "scaling_config[0].max_size"]
resource_types = ["aws_eks_node_group"]
}
transformer "file_create" {
path = "pofix_eks_node_group_scaling.tf"
content = "# Generated by the pofix rule eks_node_group_set_desired_size. Do not edit: pofix\n# rewrites this file every time it serves the module.\n#\n# terraform-aws-eks ignores desired_size after create, and the rule also ignores\n# min_size and max_size on aws_eks_node_group.this. These two resources push\n# changes to those variables with the AWS CLI instead, in one\n# update-nodegroup-config call, so raising min_size above the live desired size\n# does not fail. Requires bash, the AWS CLI and credentials for the node group's\n# account on the machine that runs terraform apply.\n\nlocals {\n pofix_scaling_script = <<-EOT\n set -euo pipefail\n fail() { echo \"pofix eks_node_group_set_desired_size: $*\" >&2; exit 1; }\n command -v aws >/dev/null 2>&1 || fail \"the AWS CLI is required on the machine that runs terraform apply\"\n for v in MIN MAX DESIRED; do\n [[ $${!v} =~ ^[0-9]+$ ]] || fail \"$v='$${!v}' is not a non-negative integer\"\n done\n (( MIN <= MAX )) || fail \"min_size ($MIN) is greater than max_size ($MAX)\"\n ng=(--cluster-name \"$CLUSTER\" --nodegroup-name \"$NODEGROUP\")\n upd=(--name \"$CLUSTER\" --nodegroup-name \"$NODEGROUP\")\n\n account=$(aws sts get-caller-identity --query Account --output text) || fail \"cannot resolve the caller's AWS account\"\n [ \"$account\" = \"$(cut -d: -f5 <<<\"$ARN\")\" ] || fail \"credentials are for account $account, the node group is $ARN\"\n\n live=$(aws eks describe-nodegroup \"$${ng[@]}\" --query 'nodegroup.scalingConfig.[minSize,maxSize,desiredSize]' --output text) \\\n || fail \"cannot describe node group $NODEGROUP\"\n read -r live_min live_max live_desired <<<\"$live\"\n for v in live_min live_max live_desired; do\n [[ $${!v} =~ ^[0-9]+$ ]] || fail \"unexpected describe-nodegroup output: '$live'\"\n done\n\n # limits: set min/max, keep the live desired size unless it falls outside them.\n # desired: set the desired size only, inside the live min/max.\n if [ \"$MODE\" = limits ]; then\n target=$(( live_desired < MIN ? MIN : live_desired > MAX ? MAX : live_desired ))\n [ \"$live_min\" = \"$MIN\" ] && [ \"$live_max\" = \"$MAX\" ] && [ \"$live_desired\" = \"$target\" ] && exit 0\n config=\"minSize=$MIN,maxSize=$MAX,desiredSize=$target\"\n else\n (( live_min <= DESIRED && DESIRED <= live_max )) \\\n || fail \"desired_size $DESIRED is outside the node group's min/max ($live_min/$live_max)\"\n [ \"$live_desired\" = \"$DESIRED\" ] && exit 0\n config=\"desiredSize=$DESIRED\"\n fi\n\n # Another update in progress rejects this one with ResourceInUseException; retry.\n err=$(mktemp)\n trap 'rm -f \"$err\"' EXIT\n deadline=$((SECONDS + 1800))\n until id=$(aws eks update-nodegroup-config \"$${ng[@]}\" --scaling-config \"$config\" --query update.id --output text 2>\"$err\"); do\n if ! grep -q ResourceInUseException \"$err\" || (( SECONDS >= deadline )); then\n cat \"$err\" >&2\n fail \"update-nodegroup-config failed\"\n fi\n sleep 30\n done\n\n while :; do\n status=$(aws eks describe-update \"$${upd[@]}\" --update-id \"$id\" --query update.status --output text)\n case $status in\n Successful) break ;;\n Failed | Cancelled) aws eks describe-update \"$${upd[@]}\" --update-id \"$id\" --query update.errors >&2; fail \"update $id ended $status\" ;;\n esac\n (( SECONDS < deadline )) || fail \"update $id still $status after 30 minutes\"\n sleep 15\n done\n echo \"pofix: $NODEGROUP scaling set to $config (update $id)\"\n EOT\n\n pofix_scaling_environment = {\n ARN = try(aws_eks_node_group.this[0].arn, \"\")\n AWS_REGION = try(split(\":\", aws_eks_node_group.this[0].arn)[3], \"\")\n AWS_DEFAULT_REGION = try(split(\":\", aws_eks_node_group.this[0].arn)[3], \"\")\n AWS_RETRY_MODE = \"standard\"\n AWS_MAX_ATTEMPTS = \"10\"\n CLUSTER = try(aws_eks_node_group.this[0].cluster_name, \"\")\n NODEGROUP = try(aws_eks_node_group.this[0].node_group_name, \"\")\n MIN = var.min_size\n MAX = var.max_size\n DESIRED = var.desired_size\n }\n}\n\n# min_size / max_size changes. Keeps the live desired size (an autoscaler's\n# choice) unless it falls outside the new range, where it is clamped.\nresource \"terraform_data\" \"pofix_scaling_limits\" {\n count = var.create ? 1 : 0\n\n triggers_replace = [aws_eks_node_group.this[0].id, var.min_size, var.max_size]\n\n provisioner \"local-exec\" {\n interpreter = [\"/bin/bash\", \"-c\"]\n command = local.pofix_scaling_script\n environment = merge(local.pofix_scaling_environment, { MODE = \"limits\" })\n }\n}\n\n# desired_size changes. Overwrites the live desired size, including an\n# autoscaler's choice. Runs after the limits, so both can change in one apply.\nresource \"terraform_data\" \"pofix_desired_size\" {\n count = var.create ? 1 : 0\n\n triggers_replace = [aws_eks_node_group.this[0].id, var.desired_size]\n\n provisioner \"local-exec\" {\n interpreter = [\"/bin/bash\", \"-c\"]\n command = local.pofix_scaling_script\n environment = merge(local.pofix_scaling_environment, { MODE = \"desired\" })\n }\n\n depends_on = [terraform_data.pofix_scaling_limits]\n}"
overwrite = true
}
}lifecycle_create_before_destroy
Stand up the replacement before destroying the old resource, so a security group still attached to an ENI or a certificate still bound to a listener can be replaced without a failed apply or an outage
| Severity | warning |
| Transformers | lifecycle (safe) |
| Page | Create Before Destroy |
rule "lifecycle_create_before_destroy" {
description = "Stand up the replacement before destroying the old resource, so a security group still attached to an ENI or a certificate still bound to a listener can be replaced without a failed apply or an outage"
severity = "warning"
param "resource_types" {
type = "list(string)"
default = ["aws_security_group", "aws_acm_certificate"]
description = "Resource types to replace create-first"
}
transformer "lifecycle" {
action = "add"
create_before_destroy = true
resource_types = param.resource_types
}
}lifecycle_ignore_ami_changes
Ignore AMI changes on EC2 instances and launch templates to prevent unnecessary replacements. On terraform-aws-ec2-instance the native ignore_ami_changes does the same but moves the instance to a new state address
| Severity | warning |
| Transformers | lifecycle (safe) |
| Page | Ignore AMI Changes |
rule "lifecycle_ignore_ami_changes" {
description = "Ignore AMI changes on EC2 instances and launch templates to prevent unnecessary replacements. On terraform-aws-ec2-instance the native ignore_ami_changes does the same but moves the instance to a new state address"
severity = "warning"
param "ignore_changes" {
type = "list(string)"
default = ["ami", "image_id"]
description = "Lifecycle attributes to ignore"
}
param "resource_types" {
type = "list(string)"
default = ["aws_instance", "aws_launch_template"]
description = "Target resource type patterns"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.ignore_changes
resource_types = param.resource_types
}
}lifecycle_ignore_autoscaling_changes
Ignore autoscaling-managed capacity on DynamoDB tables to prevent drift. On terraform-aws-dynamodb-table the native autoscaling_enabled does the same but moves the table to a new state address
| Severity | warning |
| Transformers | lifecycle (safe) |
| Page | Ignore Autoscaling Changes |
rule "lifecycle_ignore_autoscaling_changes" {
description = "Ignore autoscaling-managed capacity on DynamoDB tables to prevent drift. On terraform-aws-dynamodb-table the native autoscaling_enabled does the same but moves the table to a new state address"
severity = "warning"
param "ignore_changes" {
type = "list(string)"
default = ["read_capacity", "write_capacity"]
description = "Lifecycle attributes to ignore"
}
param "resource_types" {
type = "list(string)"
default = ["aws_dynamodb_table"]
description = "Target resource type patterns"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.ignore_changes
resource_types = param.resource_types
}
}lifecycle_ignore_deployed_artifacts
Ignore the artifact pointer a deployment pipeline rewrites on Lambda functions and ECS services, so the next Terraform run stops rolling the deployed version back. Assumes something other than Terraform deploys the code: do not enable it where Terraform builds the artifact (create_package = true) or builds the container image. Native alternatives: ignore_task_definition_changes on terraform-aws-ecs (moves the service to a new state address) and ignore_source_code_hash on terraform-aws-lambda (covers the zip hash only)
| Severity | warning |
| Transformers | lifecycle (safe), lifecycle (safe) |
| Page | Ignore Deployed Artifacts |
rule "lifecycle_ignore_deployed_artifacts" {
description = "Ignore the artifact pointer a deployment pipeline rewrites on Lambda functions and ECS services, so the next Terraform run stops rolling the deployed version back. Assumes something other than Terraform deploys the code: do not enable it where Terraform builds the artifact (create_package = true) or builds the container image. Native alternatives: ignore_task_definition_changes on terraform-aws-ecs (moves the service to a new state address) and ignore_source_code_hash on terraform-aws-lambda (covers the zip hash only)"
severity = "warning"
param "lambda_ignore_changes" {
type = "list(string)"
default = ["s3_key", "s3_object_version", "source_code_hash", "image_uri"]
description = "Attributes naming the deployed Lambda artifact (zip in S3, its version, its hash, or the container image). Override to [] to contribute nothing to the merged set"
}
param "lambda_resource_types" {
type = "list(string)"
default = ["aws_lambda_function"]
description = "Resource types carrying the Lambda artifact pointer"
}
param "ecs_service_ignore_changes" {
type = "list(string)"
default = ["task_definition"]
description = "Attributes naming the deployed ECS task definition revision. Assumes an external deployer publishes revisions; if Terraform publishes them this pins the service to a stale revision. Override to [] to contribute nothing to the merged set"
}
param "ecs_service_resource_types" {
type = "list(string)"
default = ["aws_ecs_service"]
description = "Resource types carrying the ECS task definition pointer"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.lambda_ignore_changes
resource_types = param.lambda_resource_types
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.ecs_service_ignore_changes
resource_types = param.ecs_service_resource_types
}
}lifecycle_ignore_scaling_changes
Ignore the capacity attribute an external autoscaler owns on ASGs, EKS node groups and ECS services, so Terraform stops reverting the scaler. No effect on terraform-aws-eks node groups or the terraform-aws-ecs service module, which already ignore it. On terraform-aws-autoscaling the native ignore_desired_capacity_changes does the same but moves the group to a new state address
| Severity | warning |
| Transformers | lifecycle (safe), lifecycle (safe), lifecycle (safe) |
| Page | Ignore Scaling Changes |
rule "lifecycle_ignore_scaling_changes" {
description = "Ignore the capacity attribute an external autoscaler owns on ASGs, EKS node groups and ECS services, so Terraform stops reverting the scaler. No effect on terraform-aws-eks node groups or the terraform-aws-ecs service module, which already ignore it. On terraform-aws-autoscaling the native ignore_desired_capacity_changes does the same but moves the group to a new state address"
severity = "warning"
param "autoscaling_group_ignore_changes" {
type = "list(string)"
default = ["desired_capacity"]
description = "Attributes to ignore on autoscaling groups. Override to [] to contribute nothing to the merged set"
}
param "autoscaling_group_resource_types" {
type = "list(string)"
default = ["aws_autoscaling_group"]
description = "Resource types carrying the autoscaling group capacity attribute"
}
param "eks_node_group_ignore_changes" {
type = "list(string)"
default = ["scaling_config[0].desired_size"]
description = "Attributes to ignore on EKS node groups; the desired size sits inside the scaling_config block. Override to [] to contribute nothing to the merged set"
}
param "eks_node_group_resource_types" {
type = "list(string)"
default = ["aws_eks_node_group"]
description = "Resource types carrying the EKS node group capacity attribute"
}
param "ecs_service_ignore_changes" {
type = "list(string)"
default = ["desired_count"]
description = "Attributes to ignore on ECS services. Override to [] to contribute nothing to the merged set"
}
param "ecs_service_resource_types" {
type = "list(string)"
default = ["aws_ecs_service"]
description = "Resource types carrying the ECS service capacity attribute"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.autoscaling_group_ignore_changes
resource_types = param.autoscaling_group_resource_types
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.eks_node_group_ignore_changes
resource_types = param.eks_node_group_resource_types
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.ecs_service_ignore_changes
resource_types = param.ecs_service_resource_types
}
}lifecycle_ignore_secret_rotation
Ignore the database credential attribute so a value that changes between plans - a regenerated random_password, a rotated secrets-manager or Vault data source, a variable supplied differently per runner - stops showing as a perpetual diff
| Severity | warning |
| Transformers | lifecycle (safe), lifecycle (safe) |
| Page | Ignore Secret Rotation |
rule "lifecycle_ignore_secret_rotation" {
description = "Ignore the database credential attribute so a value that changes between plans - a regenerated random_password, a rotated secrets-manager or Vault data source, a variable supplied differently per runner - stops showing as a perpetual diff"
severity = "warning"
param "db_instance_ignore_changes" {
type = "list(string)"
default = ["password"]
description = "Credential attributes on RDS instances. Override to [] to contribute nothing to the merged set"
}
param "db_instance_resource_types" {
type = "list(string)"
default = ["aws_db_instance"]
description = "Resource types whose credential attribute is named `password`"
}
param "rds_cluster_ignore_changes" {
type = "list(string)"
default = ["master_password"]
description = "Credential attributes on Aurora clusters. Override to [] to contribute nothing to the merged set"
}
param "rds_cluster_resource_types" {
type = "list(string)"
default = ["aws_rds_cluster"]
description = "Resource types whose credential attribute is named `master_password`"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.db_instance_ignore_changes
resource_types = param.db_instance_resource_types
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.rds_cluster_ignore_changes
resource_types = param.rds_cluster_resource_types
}
}lifecycle_ignore_tag_keys
Ignore drift on specific resource tag keys (e.g. an external scanner tag) via lifecycle ignore_changes, without exposing an arbitrary lifecycle value
| Severity | warning |
| Transformers | lifecycle (safe) |
| Family | lifecycle_ignore_tags — at most one rule in a family applies to a given resource type |
| Page | Ignore Selected Tag Keys |
rule "lifecycle_ignore_tag_keys" {
description = "Ignore drift on specific resource tag keys (e.g. an external scanner tag) via lifecycle ignore_changes, without exposing an arbitrary lifecycle value"
severity = "warning"
family = "lifecycle_ignore_tags"
param "tag_keys" {
type = "list(string)"
default = []
description = "AWS tag keys to ignore drift on. Each is emitted as ignore_changes = [tags[\"<key>\"]] with safe literal encoding; empty = unconfigured no-op"
}
param "resource_types" {
type = "list(string)"
default = ["aws_s3_bucket"]
description = "Target resource type patterns. A module with none of these is not applicable"
}
transformer "lifecycle" {
action = "add"
resource_types = param.resource_types
}
}lifecycle_ignore_tags
Ignore tag changes in lifecycle to prevent Terraform drift on managed resources
| Severity | warning |
| Transformers | lifecycle (safe) |
| Page | Ignore Tag Changes |
rule "lifecycle_ignore_tags" {
description = "Ignore tag changes in lifecycle to prevent Terraform drift on managed resources"
severity = "warning"
param "ignore_changes" {
type = "list(string)"
default = ["tags", "tags_all"]
description = "Lifecycle attributes to ignore"
}
param "resource_types" {
type = "list(string)"
default = ["aws_*"]
description = "Target resource type patterns"
}
transformer "lifecycle" {
action = "add"
ignore_changes = param.ignore_changes
resource_types = param.resource_types
}
}lifecycle_prevent_destroy_data
Prevent accidental destruction of data-storage resources (S3, RDS instances and clusters, DynamoDB, EFS, ElastiCache replication groups)
| Severity | error |
| Transformers | lifecycle (safe) |
| Page | Prevent Destroy Data |
rule "lifecycle_prevent_destroy_data" {
description = "Prevent accidental destruction of data-storage resources (S3, RDS instances and clusters, DynamoDB, EFS, ElastiCache replication groups)"
severity = "error"
param "resource_types" {
type = "list(string)"
default = ["aws_s3_bucket", "aws_db_instance", "aws_rds_cluster", "aws_dynamodb_table", "aws_efs_file_system", "aws_elasticache_replication_group"]
description = "Data-storage resource types to protect"
}
transformer "lifecycle" {
action = "add"
prevent_destroy = true
resource_types = param.resource_types
}
}lifecycle_prevent_destroy_encryption
Prevent destruction of KMS keys and Secrets Manager secrets
| Severity | error |
| Transformers | lifecycle (safe) |
| Page | Prevent Destroy Encryption |
rule "lifecycle_prevent_destroy_encryption" {
description = "Prevent destruction of KMS keys and Secrets Manager secrets"
severity = "error"
param "resource_types" {
type = "list(string)"
default = ["aws_kms_key", "aws_secretsmanager_secret"]
description = "Encryption resource types to protect"
}
transformer "lifecycle" {
action = "add"
prevent_destroy = true
resource_types = param.resource_types
}
}provisioner_remove_blocks
Remove provisioner blocks which are an anti-pattern in reusable modules
| Severity | warning |
| Transformers | block_removal (caution) |
| Page | No Provisioners |
rule "provisioner_remove_blocks" {
description = "Remove provisioner blocks which are an anti-pattern in reusable modules"
severity = "warning"
param "resource_types" {
type = "list(string)"
default = ["*"]
description = "Target resource type patterns"
}
transformer "block_removal" {
block_type = "provisioner"
}
}resource_restrict_instance_types
Restrict EC2 instance types to cost-effective options
| Severity | error |
| Transformers | attribute_restriction (safe) |
| Page | Restrict Instance Types |
rule "resource_restrict_instance_types" {
description = "Restrict EC2 instance types to cost-effective options"
severity = "error"
param "denied" {
type = "list(string)"
default = ["p3.*", "p4.*", "x1.*", "x2.*", "u-*"]
description = "Denied instance type patterns"
}
param "message" {
type = "string"
default = "GPU and high-memory instance types are not permitted without approval"
description = "Validation error message"
}
transformer "attribute_restriction" {
resource_type = "aws_instance"
attribute = "instance_type"
denied = param.denied
message = param.message
}
}resource_set_deletion_protection
Turn on deletion protection for databases and load balancers that leave it unset, so a stray destroy cannot remove them
| Severity | error |
| Transformers | attribute_set (safe), attribute_set (safe), attribute_set (safe) |
| Page | Set Deletion Protection |
rule "resource_set_deletion_protection" {
description = "Turn on deletion protection for databases and load balancers that leave it unset, so a stray destroy cannot remove them"
severity = "error"
transformer "attribute_set" {
attribute = "deletion_protection"
value = true
resource_types = ["aws_db_instance", "aws_rds_cluster"]
}
transformer "attribute_set" {
attribute = "deletion_protection_enabled"
value = true
resource_types = ["aws_dynamodb_table"]
}
transformer "attribute_set" {
attribute = "enable_deletion_protection"
value = true
resource_types = ["aws_lb"]
}
}s3_lifecycle_abort_incomplete_uploads
Require an enabled S3 lifecycle rule that aborts incomplete multipart uploads, so parts that are billed but never listed stop accumulating
| Severity | warning |
| Transformers | variable_assertion (safe), variable_assertion (safe) |
| Page | Abort Incomplete Multipart Uploads |
rule "s3_lifecycle_abort_incomplete_uploads" {
description = "Require an enabled S3 lifecycle rule that aborts incomplete multipart uploads, so parts that are billed but never listed stop accumulating"
severity = "warning"
param "abort_days" {
type = "string"
default = "7"
description = "Maximum days an incomplete multipart upload may linger before it must be aborted. A stricter (smaller) value in the module still passes. Default 7 - 1 day kills legitimately long-running uploads"
}
param "expire_noncurrent" {
type = "bool"
default = false
description = "Opt in to also requiring a bounded noncurrent-version expiry. Off by default; see the rule docs before enabling"
}
param "noncurrent_days" {
type = "string"
default = "90"
description = "Only read when expire_noncurrent is true. Maximum days a noncurrent version may be retained"
}
param "newer_noncurrent_versions" {
type = "string"
default = "5"
description = "Only read when expire_noncurrent is true. Minimum newer versions the expiry must retain. This is an S3 MODIFIER, not a standalone action - NoncurrentDays is what actually expires anything"
}
param "variable_name" {
type = "string"
default = "lifecycle_rule"
description = "Name of the module variable carrying the lifecycle rules"
}
transformer "variable_assertion" {
variable = param.variable_name
condition = "try(anytrue([for r in try(jsondecode(var.{{variable}}), var.{{variable}}) : try(r.enabled, tobool(r.status), lower(r.status) == \"enabled\", false) && try(tonumber(r.abort_incomplete_multipart_upload_days), 0) >= 1 && try(tonumber(r.abort_incomplete_multipart_upload_days), 0) <= 7]), false)"
error_message = "Variable {{variable}} must contain an enabled lifecycle rule that aborts incomplete multipart uploads within 7 day(s). Add an entry such as { id = \"abort-incomplete-multipart-upload\", enabled = true, abort_incomplete_multipart_upload_days = 7 }. Parts of an interrupted multipart upload are billed as storage but are not returned by ListObjects, so they accumulate unseen."
}
transformer "variable_assertion" {
variable = param.variable_name
condition = "var.object_lock_enabled ? true : try(anytrue([for r in try(jsondecode(var.{{variable}}), var.{{variable}}) : try(r.enabled, tobool(r.status), lower(r.status) == \"enabled\", false) && anytrue([for e in try(flatten([r.noncurrent_version_expiration]), []) : try(tonumber(e.days), tonumber(e.noncurrent_days), 0) >= 1 && try(tonumber(e.days), tonumber(e.noncurrent_days), 0) <= 90 && try(tonumber(e.newer_noncurrent_versions), 0) >= 5])]), false)"
error_message = "Variable {{variable}} must contain an enabled lifecycle rule whose noncurrent_version_expiration retains a noncurrent version for at most 90 day(s) while keeping at least 5 newer version(s). Buckets with var.object_lock_enabled set are exempt: Object Lock denies the expiration, so it would save nothing there."
enabled = param.expire_noncurrent
}
}variable_allowed_regions
Restrict the module's region variable to an org-approved allowlist
| Severity | error |
| Transformers | variable_assertion (safe) |
| Page | Allowed Regions |
rule "variable_allowed_regions" {
description = "Restrict the module's region variable to an org-approved allowlist"
severity = "error"
param "regions" {
type = "list(string)"
default = []
description = "Allowed AWS regions (enumerated, no globs). Empty = unconfigured: the rule injects nothing and enforces nothing"
}
param "allow_unset" {
type = "bool"
default = false
description = "Accept a null region (provider-inherited). Unenforced by consent - it does not permit a wrong region"
}
param "variable_name" {
type = "string"
default = "region"
description = "Name of the module variable carrying the region"
}
param "resource_types" {
type = "list(string)"
default = ["aws_*"]
description = "Resource types this rule applies to. A module with none is not applicable"
}
transformer "variable_assertion" {
variable = param.variable_name
condition = "var.{{variable}} == null ? {{allow_unset}} : contains({{values}}, var.{{variable}})"
error_message = "Variable \"{{variable}}\" must be one of the regions allowed by your organization: {{values}}. An empty or unknown value is rejected."
}
}Rules that are not listed here
The registry also carries a small set of internal content-processing rules.
They are not selectable — ?rules= does not accept them — so they are outside
what this page is for, which is choosing a rule to name. They are not among the
rules above and their definitions are not published.
They are not invisible either: a module compliance.tf builds for you carries a
compliancetf-manifest.json naming each rule the build received, internal ones
included, each with an outcome recording what it actually did.
Terraform and OpenTofu compatible
Every rule above reads ordinary HCL, and anything it changes stays ordinary HCL. Modules behave the same under Terraform (1.x+) and OpenTofu (1.6+).