Prevent destroy encryption
For platform engineers deciding whether to apply lifecycle_prevent_destroy_encryption: what it changes, how to enable it, and how to check it worked.
Adds prevent_destroy = true to KMS keys and Secrets Manager secrets. Blocks accidental deletion of encryption infrastructure.
What this rule does
Adds a lifecycle { prevent_destroy = true } block to each matching resource. Terraform and OpenTofu will refuse to destroy these resources during terraform destroy or resource replacement.
Affected resources
| Resource | Service | Why |
|---|---|---|
aws_kms_key | AWS KMS | Key deletion makes all encrypted data permanently inaccessible |
aws_secretsmanager_secret | AWS Secrets Manager | Secret deletion breaks applications referencing stored credentials |
Before and after
Before (a module without the change):
resource "aws_kms_key" "this" {
# ... resource configuration ...
tags = var.tags
}After (with Prevent destroy encryption applied):
resource "aws_kms_key" "this" {
# ... resource configuration ...
tags = var.tags
lifecycle {
prevent_destroy = true
}
}The only change is the rule transformation. All existing arguments, outputs, and module behavior remain identical.
The example above is illustrative. To see this rule run for real, open it in the
Rules Playground: it applies lifecycle_prevent_destroy_encryption to a
reviewed upstream module that declares the resources this rule targets, and
shows the diff the registry would serve. No account needed.
Why this rule exists
Deleting a KMS key is irreversible after the waiting period (7-30 days). Any data encrypted with that key becomes permanently inaccessible: S3 objects, RDS snapshots, EBS volumes, everything. Deleting a Secrets Manager secret can break applications that depend on stored credentials, API keys, or configuration values.
Unlike data resources where backups might help, losing a KMS key means losing access to the data itself. There is no recovery path.
Related Terraform and OpenTofu issues
| Repository | Issue | Title |
|---|---|---|
| hashicorp/terraform | #3116 | Cannot use interpolations in lifecycle attributes |
| hashicorp/terraform | #18367 | Feature request: support prevent_destroy for modules |
Limitations this rule answers
Per-module pages under Workarounds where this rule is the answer, each with the native workaround first and the diff the rule makes:
- KMS: terraform destroy scheduled deletion of a KMS key
- Secrets Manager: terraform destroy deleted a secret other systems depend on
Default configuration
These are the values the rule ships with. Parameters never travel in a module source URL, so a rule named there runs with exactly these defaults. Your organization sets them per rule, in its Baseline or in a ruleset.
| Parameter | Type | Default | Description |
|---|---|---|---|
resource_types | list(string) | ["aws_kms_key", "aws_secretsmanager_secret"] | Encryption resource types to protect |
The HCL this rule is written in, with its parameters and the transformers it runs, is published at lifecycle_prevent_destroy_encryption.
How to enable
On a framework host, add the rule on top of your organization's Baseline with ?add_rules= in the HTTPS module source:
module "kms" {
source = "https://soc2.compliance.tf/terraform-aws-modules/kms/aws?version=x.y.z&add_rules=lifecycle_prevent_destroy_encryption"
}On your organization's host, an org admin adds the rule to the Baseline, which reaches every module served from that host, or to a named ruleset that a module selects with ?ruleset=:
module "kms" {
source = "https://bobthecorp.compliance.tf/terraform-aws-modules/kms/aws?version=x.y.z&ruleset=<ruleset-name>"
}Replace x.y.z with the release you use and bobthecorp with your organization's alias. Which parameters each host accepts, and which it refuses with a 400, is on Rulesets in the console.
How to check it worked
Read compliancetf-manifest.json in the module you received. Its entry for this rule carries an outcome: enforced means the change was made, not_applicable means nothing matched - no resource of a targeted type, or the module already carried the identical change. Do not infer enforcement from a successful terraform init; a rule that changes nothing also inits cleanly.
Known limits
- Does not cover
aws_kms_alias,aws_kms_grant, or other KMS-related resources. - Does not prevent KMS key disabling (only deletion). A disabled key can be re-enabled.
- Does not cover AWS Certificate Manager (ACM) certificates or other encryption-adjacent resources.
- For
prevent_destroy,addmeans ensure this value, not add it if absent. A resource that explicitly declaresprevent_destroy = falseis rewritten totrue; older engine releases left it unprotected while the run still reported the rule as applied. A value that is not atrue/falseliteral is left alone and warned about instead of being rewritten. - 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. - Works with Terraform 1.x and OpenTofu 1.6 and later. The rule reads ordinary HCL, and anything it changes stays ordinary HCL.
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 | Supports protecting encryption keys from destruction |
| PCI DSS 4.0 | 3.6, 3.7 | Supports cryptographic key management lifecycle |
Break-glass path
On a framework host, add ?rules=-lifecycle_prevent_destroy_encryption to the module source URL and run terraform init; the source changed, so Terraform installs a fresh copy without this rule. When you are done, remove the override and run terraform init again to restore it. On your organization's host the parameter is refused with a 400 rather than ignored. The rule comes from the organization's configuration: a Baseline rule applies to every module served from the bare organization form, so an admin disables it for the duration of the operation, or keeps it out of the Baseline and in a ruleset that only the modules needing protection select. On a project environment the posture is the bound snapshot and its waivers. See Baseline rules and rulesets.
Failure modes
| Scenario | Result |
|---|---|
| KMS key scheduled for deletion needs to be kept | Cancel the deletion schedule via AWS Console or CLI. The prevent_destroy block prevents Terraform from initiating deletion. |
| You need to intentionally delete a KMS key | Use the break-glass path to download the module without this rule, then run terraform destroy. |
Related
- KMS workarounds
- Secrets Manager workarounds
- Prevent destroy data
- Rulesets in the console - where an org admin adds this rule to the Baseline or a ruleset
- Rule catalog - every rule, with what each one changes