Creates one AWS Recycle Bin retention rule (
aws_rbin_rule) — an account/Region-wide, retroactive-going-forward safety net that intercepts deletes of matching EBS snapshots or AMIs and retains them for a set period, with an optional tamper-resistant lock. Built for the AWS provider v6.x.
- 🗑️ Creates one Recycle Bin retention rule that protects
EBS_SNAPSHOTorEC2_IMAGE(AMI) resources from accidental or malicious permanent deletion — a deleted matching resource is moved into the Recycle Bin instead of destroyed outright. - 🎯 Two rule shapes, both supported: a tag-level rule (
resource_tagsset — protects only tagged resources) or a Region-level rule (resource_tagsempty, optionalexclude_resource_tags— protects everything of that type in the Region except explicit exclusions). - 🔒 Tamper-resistant locking (
lock_configuration) — once locked, the retention period cannot be shortened and the rule cannot be deleted by anyone, including a fully-privilegedrbin:*principal, until an explicit unlock and a 7–30 day unlock-delay period elapse. This module validates the real AWS constraint that only a bare Region-level rule can be locked at plan time, before it can fail atapply. - ⏪ Acts retroactively going forward, not retroactively backward — from the moment the rule reaches
available, every subsequent matching delete is intercepted account/Region-wide; it has no effect on anything already deleted. - 🔗 Emits
id,status,lock_state,lock_end_time, andtags_allfor wiring into runbooks, compliance reporting, and EventBridge-based alerting on lock-state changes.
💡 Why it matters: For a regulated FI, the ability to make a retention rule's protection itself tamper-resistant — immune even to a compromised or over-privileged IAM principal — is the single strongest control available against accidental or malicious deletion of snapshots and AMIs that may back PII-bearing volumes.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
tf-mod-aws-rbin is a leaf/consumer control — per SCOPE.md it consumes nothing from other tf-mod-aws-* modules; it matches EBS snapshots / AMIs by resource_type + tag (not by id/ARN) and stands alongside those modules as an account-level safety net.
flowchart LR
EBS["tf-mod-aws-ebs-volume<br/>(snapshot lifecycle, matched by tag)"]
AMI["tf-mod-aws-ami<br/>(matched by tag)"]
THIS["tf-mod-aws-rbin<br/>(THIS MODULE)"]
EB["tf-mod-aws-eventbridge<br/>(lock-state events, out of suite)"]
EBS -.->|"resource_type=EBS_SNAPSHOT + tag match"| THIS
AMI -.->|"resource_type=EC2_IMAGE + tag match"| THIS
THIS -.->|"RuleLocked / RuleUnlocked events"| EB
style THIS fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
This module consumes no direct outputs from other tf-mod-* modules (it matches resources by resource_type + tag rather than by reference); it emits id / lock_state for runbook and EventBridge correlation — see the Typical wiring table.
A single aws_rbin_rule.this keystone with dynamic blocks for the two mutually-exclusive rule shapes and the optional tamper-resistant lock.
flowchart TD
subgraph mod["tf-mod-aws-rbin"]
THIS["aws_rbin_rule.this<br/>(keystone)<br/>resource_type + retention_period"]
RT["resource_tags<br/>(dynamic) — tag-level rule"]
ERT["exclude_resource_tags<br/>(dynamic) — Region-level rule"]
LOCK["lock_configuration<br/>(dynamic, 0 or 1) — tamper-resistant lock"]
end
THIS --> RT
THIS --> ERT
THIS --> LOCK
style THIS fill:#FF9900,color:#fff,stroke:#cc7a00,stroke-width:2px
style LOCK stroke-dasharray: 5 5
| Resource / block | Count | Created when |
|---|---|---|
aws_rbin_rule.this |
1 | always (keystone) |
resource_tags |
0..n | caller sets non-empty var.resource_tags (tag-level rule) |
exclude_resource_tags |
0..n | caller sets non-empty var.exclude_resource_tags (Region-level rule) |
lock_configuration |
0 or 1 | var.lock_configuration != null — only valid on a bare Region-level rule |
tf-mod-aws-rbin/
├── providers.tf # terraform >= 1.12, aws >= 6.0 < 7.0 (no provider{} block)
├── variables.tf # description, resource_type, resource_tags, retention_period,
│ # lock_configuration, exclude_resource_tags, tags, timeouts
├── main.tf # aws_rbin_rule.this
├── outputs.tf # id, status, lock_state, lock_end_time, tags_all
├── README.md
└── SCOPE.md
Smallest working call — a tag-level rule retaining tagged EBS snapshots for 30 days:
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Retain deleted prod EBS snapshots for 30 days"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" }
]
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
tags = {
Environment = "prod"
CostCenter = "platform"
}
}Wire it alongside the resources it protects:
module "app_data_volume" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-ebs-volume?ref=v1.0.0"
tags = { Environment = "prod" } # matches snapshot_retention.resource_tags above
#...
}
⚠️ Pin the source to a tag (?ref=v1.0.0) — never a branch.
Least-privilege actions the executing Terraform identity needs (verified against the AWS "Control access to Recycle Bin with IAM" guide).
| Action | Required for | Notes |
|---|---|---|
rbin:CreateRule |
Rule creation (apply) |
Core create action. |
rbin:GetRule |
Read / refresh state (plan) |
— |
rbin:ListRules |
Import / drift detection | — |
rbin:UpdateRule |
In-place updates (description, retention period, tags) | Rejected by the API while the rule is locked. |
rbin:DeleteRule |
Destroy | Rejected by the API while the rule is locked. |
rbin:LockRule |
Applying a non-null lock_configuration |
Only valid on a bare Region-level rule (see Architecture Notes). |
rbin:UnlockRule |
Removing/lowering lock_configuration |
Starts the unlock-delay clock; does not immediately unlock. |
rbin:TagResource |
tags at create/update |
— |
rbin:UntagResource |
Tag removal | — |
rbin:ListTagsForResource |
Tag read-back | — |
Not ARN-scopable at create time — the rule id doesn't exist yet when
CreateRuleruns. Narrow beyondResource: "*"using therbin:Request/ResourceType(onCreateRule/ListRules) orrbin:Attribute/ResourceTypecondition keys instead.ℹ️ No service-linked role, no
iam:PassRole— Recycle Bin creates no SLR, and a retention rule is not an IAM principal.
- Service-linked roles: none required.
- Account opt-ins: none — Recycle Bin is on by default in every active Region.
- Scope is account + Region + resource-type wide, not per-resource — a rule matches by
resource_type(+ tags for a tag-level rule), never by naming a specific snapshot/AMI id. It acts retroactively going forward only: it has no effect on resources deleted before it existed. - Two mutually exclusive rule shapes (see Architecture Notes for the full detail): tag-level (
resource_tagsset) and Region-level (resource_tagsempty, optionalexclude_resource_tags). Only a bare Region-level rule (both tag lists empty) is eligible forlock_configuration. - Region constraints: none — no us-east-1 global-service coupling. Rules are Region-scoped; the module inherits the caller's provider Region.
- Service quotas: default 100 retention rules per Region per account (adjustable via Service Quotas).
| This module output | Feeds into |
|---|---|
id |
Runbooks / terraform import key; correlate against GetRule/EventBridge events, which reference the rule by this id. |
status |
Post-apply health checks — confirm "available" before relying on the rule. |
lock_state |
Compliance reporting; alerting on unexpected pending_unlock transitions (pairs with an EventBridge rule on RuleUnlockScheduled in a future tf-mod-aws-eventbridge wiring). |
lock_end_time |
Runbooks during an active unlock window — when the rule becomes modifiable/deletable again. |
tags_all |
Governance / audit of effective tags (incl. provider default_tags). |
ℹ️ There is no
arnoutput —aws_rbin_ruleexposes no ARN attribute. See Architecture Notes.
ID format — id is the RBin-assigned rule identifier (e.g. rule-0123456789abcdef0). This is what the RBin API (GetRule / UpdateRule / DeleteRule) and terraform import use to reference the rule.
No ARN — a documented gap, not an oversight. The aws_rbin_rule resource (verified against hashicorp/aws v6.54.0) exports only id, status, lock_state, and lock_end_time. Recycle Bin references rules by id, and IAM scopes rbin:* actions via the rbin:Request/ResourceType / rbin:Attribute/ResourceType condition keys rather than a resource ARN. Primary outputs for this module are id + tags_all, not id + arn — adding an arn output here would reference a nonexistent attribute and fail terraform validate.
Two rule shapes, not composable filters:
- Tag-level rule —
resource_tagsnon-empty. Protects only resources carrying the matching tag(s). Narrow blast radius. - Region-level rule —
resource_tagsempty;exclude_resource_tagsoptionally non-empty. Protects every resource ofresource_typein the Region except excluded tags. Broader — and, for a regulated FI, often the more protective default.
Setting both resource_tags and exclude_resource_tags non-empty is rejected by a validation {} block — the AWS API's own Basic-Usage vs Region-Level examples show these as two distinct modes, not a combinable filter.
lock_configuration eligibility (the key gotcha): per AWS, "You can't lock tag-level retention rules, or Region-level retention rules that have exclusion tags." Only a bare Region-level rule — resource_tags empty and exclude_resource_tags empty — can be locked. This module enforces that with cross-variable validation {} blocks so a misconfigured lock attempt fails at terraform plan, not at apply against the live RBin API.
Locking tradeoffs:
- Once locked,
UpdateRuleandDeleteRuleare rejected by the API for any principal until the rule is explicitly unlocked and the unlock-delay period elapses. - The unlock-delay value (7–30 days) cannot be changed once locked — only re-locked, which resets the clock. Choose it deliberately; it should exceed your realistic incident-detection-and-response window.
- Unlocking does not immediately unlock — it starts the delay clock (
lock_statetransitions topending_unlock,lock_end_timeis populated).
tags ↔ tags_all ↔ default_tags — var.tags flows to aws_rbin_rule.this.tags. The computed tags_all output is the merge of resource tags over the provider's default_tags; resource tags win on key conflict. default_tags is the caller's provider-block concern, never set inside this module.
Eventual consistency — a freshly created rule starts in status = "pending" and transitions to "available" shortly after; only "available" rules actively retain deletions.
Destroy ordering / lock interaction — destroying a locked rule fails outright; unlock it first (a lock_configuration update to null) and wait out the unlock-delay period before the destroy will succeed.
Region — retention rules are Region-scoped, not global; there is no us-east-1 constraint. Multi-Region coverage requires one module call per Region (provider aliases).
1 · Minimal — tag-level EBS snapshot rule
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" }
]
retention_period = {
retention_period_value = 14
retention_period_unit = "DAYS"
}
}2 · With tags
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Retain tagged prod EBS snapshots"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" }
]
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
tags = {
Environment = "prod"
Application = "core-banking"
Owner = "platform-team"
}
}
tagsmerge with providerdefault_tags; resource tags win on key conflict. Inspect the effective set via thetags_alloutput.
3 · EC2_IMAGE (AMI) variant — tag-level rule
module "ami_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Retain deleted golden-image AMIs for 90 days"
resource_type = "EC2_IMAGE"
resource_tags = [
{ resource_tag_key = "ImageClass", resource_tag_value = "golden" }
]
retention_period = {
retention_period_value = 90
retention_period_unit = "DAYS"
}
}4 · Region-level rule — protect every EBS snapshot in the Region
module "region_wide_snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Region-wide safety net for all deleted EBS snapshots"
resource_type = "EBS_SNAPSHOT"
# resource_tags left empty (default []) -> Region-level rule
retention_period = {
retention_period_value = 7
retention_period_unit = "DAYS"
}
}Broader than a tag-level rule by design — every EBS snapshot deleted in the Region is retained for 7 days, no tag required. This is also the only shape eligible for
lock_configuration(Example 5).
5 · Locked Region-level rule (recommended for production)
module "locked_snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Locked, tamper-resistant retention for all prod-account EBS snapshots"
resource_type = "EBS_SNAPSHOT"
# resource_tags and exclude_resource_tags both empty -- required for locking
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
lock_configuration = {
unlock_delay = {
unlock_delay_value = 14
unlock_delay_unit = "DAYS"
}
}
tags = { Environment = "prod", Compliance = "locked-retention" }
}
⚠️ Once applied, this rule's 30-day retention cannot be shortened and the rule cannot be deleted by anyone until it is explicitly unlocked and the 14-day unlock delay elapses. Choose the unlock delay based on your realistic incident-response window — it cannot be changed later without re-locking.
6 · Region-level rule with exclusions
module "region_wide_with_exclusions" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Protect all AMIs except those explicitly marked ephemeral"
resource_type = "EC2_IMAGE"
exclude_resource_tags = [
{ resource_tag_key = "Lifecycle", resource_tag_value = "ephemeral" }
]
retention_period = {
retention_period_value = 14
retention_period_unit = "DAYS"
}
}
⚠️ A Region-level rule with exclusion tags is not eligible forlock_configuration— only a bare Region-level rule (noresource_tags, noexclude_resource_tags) can be locked (see Example 5). Settinglock_configurationhere failsterraform planwith a clear error.
7 · Custom description and short retention (dev/test)
module "dev_snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Short-window safety net for dev EBS snapshots"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "dev" }
]
retention_period = {
retention_period_value = 1
retention_period_unit = "DAYS"
}
}8 · Multiple tag conditions on one tag-level rule
module "multi_tag_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" },
{ resource_tag_key = "DataClass", resource_tag_value = "npi" }
]
retention_period = {
retention_period_value = 45
retention_period_unit = "DAYS"
}
}Multiple
resource_tagsentries are OR'd by the RBin API — a snapshot matching any listed tag is retained, not only one matching all.
9 · Customer-managed KMS — N/A for this resource
A Recycle Bin retention rule is a control-plane policy object — it holds no data of its own (the underlying EBS snapshots/AMIs it protects carry their own encryption, configured in tf-mod-aws-ebs-volume / tf-mod-aws-ami). This module therefore exposes no kms_key_arn variable: there is nothing here for a CMK to encrypt.
# No kms_key_arn here -- intentionally. Encrypt the protected snapshots/AMIs
# themselves in tf-mod-aws-ebs-volume / tf-mod-aws-ami, not the retention rule.
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
resource_type = "EBS_SNAPSHOT"
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
}10 · Secure-by-default opt-out — deliberately unlocked production rule
The secure-default recommendation is to lock production retention rules (Example 5). An explicit, documented exception to leave a production rule unlocked:
module "prod_unlocked_by_exception" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Unlocked by exception -- team needs to tune retention during rollout"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" }
]
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
# lock_configuration intentionally omitted (null) -- revisit once retention is finalized
tags = { Exception = "unlocked-retention-rollout-tuning" }
}Revisit and lock once the retention value is finalized — locking is cheap to add later, but the unlock-delay value cannot be changed after the fact.
11 · `for_each` pattern — one rule per resource type
locals {
retention_rules = {
snapshots = {
resource_type = "EBS_SNAPSHOT"
tag_value = "prod"
days = 30
}
amis = {
resource_type = "EC2_IMAGE"
tag_value = "prod"
days = 90
}
}
}
module "retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
for_each = local.retention_rules
description = "Retention for ${each.value.resource_type}"
resource_type = each.value.resource_type
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = each.value.tag_value }
]
retention_period = {
retention_period_value = each.value.days
retention_period_unit = "DAYS"
}
tags = { Environment = "prod" }
}One
aws_rbin_ruleprotects exactly oneresource_type—for_eachis the pattern for standing up the full set (snapshots + AMIs) from one module block.
12 · Import an existing retention rule
import {
to = module.existing_rule.aws_rbin_rule.this
id = "rule-0123456789abcdef0"
}
module "existing_rule" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
resource_type = "EBS_SNAPSHOT"
resource_tags = [
{ resource_tag_key = "Environment", resource_tag_value = "prod" }
]
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
}Match
resource_type,resource_tags/exclude_resource_tags, andretention_periodto the real rule's configuration before import, or the next plan shows a diff.
13 · Verify rule status and lock state post-apply
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
resource_type = "EBS_SNAPSHOT"
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
lock_configuration = {
unlock_delay = { unlock_delay_value = 7, unlock_delay_unit = "DAYS" }
}
}
output "rule_status" {
value = module.snapshot_retention.status
}
output "rule_lock_state" {
value = module.snapshot_retention.lock_state
}14 · Multi-Region — same rule in two Regions (provider aliases)
provider "aws" {
alias = "use1"
region = "us-east-1"
}
provider "aws" {
alias = "usw2"
region = "us-west-2"
}
module "retention_use1" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
providers = { aws = aws.use1 }
resource_type = "EBS_SNAPSHOT"
resource_tags = [{ resource_tag_key = "Environment", resource_tag_value = "prod" }]
retention_period = { retention_period_value = 30, retention_period_unit = "DAYS" }
}
module "retention_usw2" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
providers = { aws = aws.usw2 }
resource_type = "EBS_SNAPSHOT"
resource_tags = [{ resource_tag_key = "Environment", resource_tag_value = "prod" }]
retention_period = { retention_period_value = 30, retention_period_unit = "DAYS" }
}Retention rules are Region-scoped — register one per Region you provision snapshots/AMIs in.
15 · End-to-end composition — locked Region-level rules wired alongside protected resources
module "app_data_volume" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-ebs-volume?ref=v1.0.0"
tags = { Environment = "prod", DataClass = "npi" }
#... size, availability_zone, kms_key_arn (from tf-mod-aws-kms)...
}
module "golden_ami" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-ami?ref=v1.0.0"
tags = { ImageClass = "golden" }
#... image build details...
}
# One locked, Region-wide rule protecting every EBS snapshot in the account/Region
module "snapshot_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Locked account-wide safety net for all EBS snapshots"
resource_type = "EBS_SNAPSHOT"
retention_period = {
retention_period_value = 30
retention_period_unit = "DAYS"
}
lock_configuration = {
unlock_delay = { unlock_delay_value = 14, unlock_delay_unit = "DAYS" }
}
tags = { Environment = "prod" }
}
# A narrower, tag-scoped rule for golden AMIs -- kept unlocked so the image
# pipeline can retune retention during rollout (Example 10 pattern)
module "ami_retention" {
source = "git::https://github.com/microsoftexpert/tf-mod-aws-rbin?ref=v1.0.0"
description = "Golden AMI retention (unlocked during pipeline rollout)"
resource_type = "EC2_IMAGE"
resource_tags = [
{ resource_tag_key = "ImageClass", resource_tag_value = "golden" }
]
retention_period = {
retention_period_value = 90
retention_period_unit = "DAYS"
}
}The Region-level snapshot rule protects
app_data_volume's snapshots (and every other EBS snapshot in the Region) without needing to reference it by id or ARN — Recycle Bin matches byresource_type(+ tags), not by resource reference, which is why this module consumes nothing fromtf-mod-aws-ebs-volume/tf-mod-aws-amidirectly.
Core
description(string, defaultnull) — human-readable description; the closest thing to a "name" this resource has.resource_type(string, required) —"EBS_SNAPSHOT"or"EC2_IMAGE".retention_period(object, required) —{ retention_period_value = number, retention_period_unit = optional(string, "DAYS") }.
Rule scope
resource_tags(list(object), default[]) — tag-level rule scope; each{ resource_tag_key, resource_tag_value = optional }.exclude_resource_tags(list(object), default[]) — Region-level rule exclusions; same shape. Mutually exclusive with a non-emptyresource_tags.
Locking
lock_configuration(object, defaultnull) —{ unlock_delay = { unlock_delay_value = number (7-30), unlock_delay_unit = optional(string, "DAYS") } }. Only valid when both tag lists above are empty.
Universal
tags(map(string), default{}).timeouts(object, default{}— provider defaults 30m create/update/delete apply).
id— the rule identifier (rule-...); the RBin API andterraform importreference key.status—"pending"or"available".lock_state—"locked"/"pending_unlock"/"unlocked".lock_end_time— unlock-delay expiry timestamp (populated only whilepending_unlock).tags_all— merged tags incl. providerdefault_tags.
No
sensitiveoutputs — this resource holds no secret material. Noarnoutput — the resource exposes none (see Architecture Notes).
| Posture | Default | Opt-out / opt-in |
|---|---|---|
| Rule scope | resource_tags empty (no forced tag filter) |
Set resource_tags for a narrow tag-level rule, or deliberately choose the broader Region-level shape (often the more protective choice for a regulated FI) by leaving it empty |
Tamper-resistant retention (lock_configuration) |
null (unlocked) |
Recommended for production — set a non-null lock_configuration on a bare Region-level rule; the friction of an immutable retention period is deliberate |
| Unlock delay | No implicit default; validated to AWS's enforced 7-30 day range | Choose the longest delay your incident-response process can tolerate — it cannot be shortened after locking |
Additional principles:
- Single resource named
this— exactly four files; noprovider {}block; no credential orregionvariable. - Real API constraints enforced at plan time, not apply time — the lock/tag-shape mutual exclusions are validated in
variables.tfso a misconfiguration failsterraform planinstead of the live RBin API. - Primary outputs
id+tags_all(notid+arn— this resource exposes no ARN, documented rather than fabricated).
cd C:\GitHubCode\newawsmodules\tf-mod-aws-rbin
terraform init -backend=false
terraform validate
terraform fmt -check
plan/applyrequire valid AWS credentials (profile / SSO / OIDC) and a configured provider Region. This module declares onlyrequired_providersand inherits the caller's provider.
⚠️ Pin the source with?ref=v1.0.0, never a branch.
| Symptom | Likely cause | Fix |
|---|---|---|
lock_configuration cannot be set on a tag-level rule (plan-time validation error) |
lock_configuration set together with non-empty resource_tags |
Locking only applies to a bare Region-level rule — clear resource_tags or drop lock_configuration. |
lock_configuration cannot be set on a Region-level rule that has exclusion tags |
lock_configuration set together with non-empty exclude_resource_tags |
Clear exclude_resource_tags or drop lock_configuration — only a rule with both tag lists empty can be locked. |
Set resource_tags... or exclude_resource_tags..., not both |
Both tag lists non-empty | These are two different rule shapes (tag-level vs Region-level) — populate only one. |
apply fails updating/deleting a rule that plans cleanly |
The rule is currently locked | Expected — UpdateRule/DeleteRule are rejected by AWS while locked. Unlock first and wait out the unlock-delay period. |
| Rule appears created but isn't retaining deletions yet | status is still "pending", not "available" |
Check the status output; newly created rules transition shortly after apply. |
apply fails UnauthorizedOperation / AccessDenied |
Missing IAM action (e.g. rbin:CreateRule, rbin:LockRule) |
Grant the least-privilege action from Required IAM Permissions; don't broaden to *. |
| Tag drift on every plan | Same key in both provider default_tags and module tags |
Resource tags win — remove the duplicate from one side; inspect tags_all. |
Confusion looking for an arn output |
This resource exports no ARN attribute | Reference the rule by its id output instead — that's what the RBin API itself uses. |
| Unlock delay can't be changed | Rule was already locked with a different unlock_delay_value |
Expected — AWS fixes the unlock delay once locked. Re-lock (which resets the clock) if you must change it. |
- Terraform
aws_rbin_ruleresource - AWS — What is Recycle Bin?
- AWS — Lock a Recycle Bin retention rule
- AWS — Control access to Recycle Bin with IAM
- AWS — Monitor Recycle Bin using Amazon EventBridge
- Sibling modules:
tf-mod-aws-ebs-volume,tf-mod-aws-ami
🧡 "Infrastructure as Code should be standardized, consistent, and secure."