terraform-aws-modules/terraform-aws-iam examples/iam-read-only-policy
Source: https://github.com/terraform-aws-modules/terraform-aws-iam.git at v6.8.0.
Set: core. Lane: terraform-popular.
Why it is in the core set: a most-downloaded terraform-aws-modules example, pinned by tag; the shape most people deploy
Not clear yet.
| Stage | Verdict | Detail |
|---|---|---|
| Cold deploy | pass | Apply complete! Resources: 1 added, 0 changed, 0 destroyed.; 0 objects carry tofu-estate=iam-read-only-policy-crossing before migration |
| Migrate | pass | 1 of 1 stamped, carrying tofu-slot=0 read back through IAM (choudoufu #372); Apply complete! Resources: 0 added, 0 changed, 0 destroyed. - nothing left to converge |
| Replan from nothing | pass | no resource change proposed, nothing foreign; identity re-check (via the AWS CLI) unchanged |
| No-op apply | pass | genuine no-op: 1 objects before, 1 after, no state file either time |
| Drift and reconverge | pass | one object tampered (arn:aws:iam::000000000000:policy/example/ex-iam-read-only-policy-82e4bb26d77dc89263bfa1c0be’s Example tag), plan proposed fixing exactly module.read_only_iam_policy.aws_iam_policy.policy[0], apply changed 1 and reconverged the tag |
| Rename | pass | moved block: module.read_only_iam_policy renamed to module.read_only_iam_policy_moved with zero churn (0 add, 1 change, 0 destroy), tofu-address marker rewritten in place; live-mv: module.read_only_iam_policy_moved renamed to module.read_only_iam_policy_final with zero churn, marker rewritten in place; stock oracle over the identical net rename on cold_deploy’s own state also shows a true no-op (0 add, 0 change, 0 destroy, outputs unchanged in value); the live policy ARN unchanged throughout, read via the AWS CLI |
| Remove a block | pass | choudoufu: deleting module.read_only_iam_policy_final’s block proposed exactly one destroy (0 add, 0 change, 1 destroy), applied cleanly (0 added, 0 changed, 1 destroyed), the object is genuinely gone from the live account (iam get-policy on the old ARN now returns NoSuchEntity, read via the AWS CLI, not choudoufu’s own report), and the next plan proposes no resource action; classifyOrphans did not withhold the destroy because no other aws_iam_policy.policy block anywhere in this config ever declares a real instance (count=0 on both remaining module calls) |
| Change count (planned) | not run | |
| Replace with create_before_destroy | not run | |
| Crash between create and destroy (planned) | not run | |
| Teardown (planned) | not run | |
| Plan, review, apply (planned) | not run | |
| Greenfield apply | pass | 1 resource from nothing, marker verified via the AWS CLI, 1 record in the local record store (#364 A2), replan empty, stock oracle in its own namespace matches structurally (path, description, policy document) |
| Strict profile (planned) | not run |
Last run at commit 2d8978675e on 2026-08-25T17:48:36Z, exit code 0.
Upgraded from a real but pre-#274-pipeline predecessor script (choudoufu apply from a live block present from the start, delete state, replan empty twice) to the current five-stage shape, same upgrade as corpus-iam-policy and following the same corpus-vpc-complete/corpus-lambda-simple structure. Verified for real in a fresh isolated worktree off local main (ff106e63a7), Docker/floci/AWS CLI throughout. All five stages pass cleanly: cold_deploy (plain terraform apply, “Apply complete! Resources: 1 added” - only the first of this module’s three instantiations contributes a resource, confirmed 0 objects tagged before migration), migrate (live-import dry run verifies “1 of 1 resource instance(s) are eligible for stamping”, -approve reports “1 resource(s) newly stamped, 0 already stamped, 0 failed, 0 skipped”, tofu-address=module.read_only_iam_policy.aws_iam_policy.policy:0 read directly through the AWS CLI against the real, server-assigned name IAM minted), test_plan (live-plan genuinely empty, identity re-read unchanged after the state file’s only copy was deleted), test_apply (“0 added, 0 changed, 0 destroyed”, object count unchanged at 1), and drift_reconverge (the one policy’s Example tag tampered directly against floci, live-plan proposes fixing exactly that object by address, apply reconverges to “0 added, 1 changed, 0 destroyed”). Same tofu-slot finding as corpus-iam-policy (see that entry) applies identically here - the module’s aws_iam_policy also declares count = var.create && var.create_policy ? 1 : 0 - and is folded into stage 2 the same way (“0 added, 1 changed, 0 destroyed” before stage 3 is attempted). Genuine constraint found here that corpus-iam-policy does not share: this estate creates exactly ONE real object (the other two module calls contribute nothing), so stage 5’s BREAK=1 cannot prove non-vacuousness by tampering a second object the way corpus-iam-policy’s or corpus-vpc-complete’s can - there isn’t one. Used the address-corruption technique instead (the same shape and resource type, naming a module that in fact creates nothing) and verified it independently at both sites it appears: run as committed it fails stage 3’s identity check; run separately with stage 3’s corruption disabled, it correctly fails stage 5’s exact-address assertion instead. That second, isolated verification also caught a real bug in this script’s first draft: the exact-equality comparison against a live-plan diff header’s address (bracket form, “policy[0]”) was written against the escaped tag-value form (“policy:0”) and could never have matched even on the correct object - fixed by keeping both address forms as separate variables, and the fix was re-verified with a full clean pass afterward.
Reproduce it#
go run ./tools/gauntlet run corpus-iam-read-only-policyNeeds Docker (the emulator is pulled at the pinned digest), the AWS CLI, and a
stock terraform or tofu binary on PATH for the cold deploy. The script is
live/e2e/corpus-iam-read-only-policy/run.sh; BREAK=1 corrupts its assertions to show they are load-bearing.