Repository navigation
test(iacfiles): find Terraform IAM statements without a Sid - #1214
Conversation
TestMemberAccountPoliciesTagReservedInstancesOnly read the two member-account Terraform policies by anchoring on `Sid =`, so a statement written without a Sid was never examined: a Sid-less grant of ec2:CreateTags on Resource "*" passed the test. Replace the Sid-anchored scan with a small HCL tokenizer (strings, template sequences, comments, heredocs, brackets) that treats every object constructor or block body with an Action / actions attribute as a statement. It fails closed: a non-literal Action or Resource, a repeated key, or a mismatch between the statements it parsed and the Action keys counted in the raw text is an error, not a silent skip. The module has no HCL dependency, so none is added. Pin it with mutants of the real templates: Sid-less list, scalar, one-line, quoted-key and comment-wrapped statements, a Sid-less aws_iam_policy_document block and the real tagging statement widened in place must all be caught; a Sid-carrying control shows the old scan's coverage. Closes LeanerCloud#782 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 2 billable files and costs up to $0.50. Or wait 59 minutes for your next included review. View limit details
Comment |
|
Independent review 2 (claude-fleet, cold): APPROVE 406b184 Findings (none blocking):
Against #782:
Attacked:
Verified locally, with no cloud credentials and GOPROXY=off, under the heavy lock:
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
VERDICT: APPROVE 406b184 Independent adversarial review of #1214 (closes #782) at 406b184, base main. Test-only change in internal/iacfiles. The fix does what #782 asks: the Sid-anchored text scan is gone, and every object constructor or block body with an Action/actions key now counts as a statement whether or not it has a Sid. Non-literal values and statements the tokenizer cannot see fail closed. Findings (none blocking):
Attacked:
|
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 1 approving independent review(s) on |
Root cause
TestMemberAccountPoliciesTagReservedInstancesOnly(#702, PR 773) read the two Terraform member-account policies (iac/federation/aws-cross-account/terraform/main.tf,iac/federation/aws-target/terraform/main.tf) throughtfStatements. That function found statements by anchoring onSid\s*=and taking the brace-balanced object around each match.Sidis optional, so a statement written without one was never examined. A Sid-less{ Effect = "Allow", Action = ["ec2:CreateTags"], Resource = "*" }passed the test, and the role could then retag every EC2 resource. The CloudFormation files (YAML walk) and the CLI heredocs (JSON) were not affected.Fix (test-only)
The module has no HCL dependency (
hashicorp/hclis in neithergo.modnorgo.sum), so this PR adds none. Instead it uses a statement-boundary scan that never looks atSid:internal/iacfiles/tfpolicy_scan_test.go(new): a small HCL tokenizer that knows where quoted strings (including${...}/%{...}template sequences with nested quotes),#/////* */comments, heredocs and brackets begin and end. Every object constructor or block body with anActionattribute (oractions, foraws_iam_policy_documentstatement blocks) counts as one statement. Keys match case-insensitively, and both=and:separators work. ItsAction/Resourceare read as one literal string or a list of them.ActionorResource(a reference, a function call, an interpolation);Action/Resourcekey;Actionkeys counted by a regex over the raw text. This catches anything the tokenizer cannot see, such as a JSON policy in a heredoc.internal/iacfiles/templates_aws_tagging_test.go: the old Sid-anchoredtfStatementsand its regexes are removed. The fix(iac): grant ec2:CreateTags to member-account purchase roles so the idempotency tag is written #702 assertions move intocreateTagsViolations, so the mutation tests can reuse them. The main test's behavior on the real templates is unchanged.Other tests in
internal/iacfiles: I checked them for the same Sid-anchored scan and none use it.templates_aws_wif_test.godecodes JSON, the CLI heredocs and CFN templates go throughencoding/json/yaml.v3, andtemplates_azure_wif_test.goonly countsrunblocks.tfStatementswas the only text scan of IAM statements, so nothing else needed hardening.Closes #782
Verification
All evidence comes from unit tests with fixtures and mutations. Nothing calls a cloud API or runs terraform. Tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/nullbecause this machine's default AWS profile is invalid. The package doesn't need AWS config.Real templates still pass, and every statement is seen.
go test ./internal/iacfiles/ -count=1passes. A throwaway debug run (not committed) showed the new scan finds 11 statements in the cross-account file (10 permission statements, theOrganizationsDiscoverystatement and the trustaws_iam_policy_documentstatement) and 10 in the target file (9 permission statements and the trust statement). In each file exactly oneec2:CreateTagsis scoped toarn:aws:ec2:*:*:reserved-instances/*.Fail-before on main, against the real templates. I copied main's
internal/iacfilesand the IaC files into amktemp -dscratch module and inserted a Sid-less statement beforeCostExplorerin bothmain.tfcopies:{ Effect = "Allow" Action = ["ec2:CreateTags"] Resource = "*" }--- PASS: TestMemberAccountPoliciesTagReservedInstancesOnly(bothterraform/main.tfsubtests PASS, so the widening is undetected).terraform/main.tfsubtests FAIL withec2:CreateTags must be scoped to arn:aws:ec2:*:*:reserved-instances/*, got Resource "*"andec2:CreateTags must appear exactly once, got 2.Committed regression test, before and after.
TestTFStatementsCatchWidenedCreateTagsmutates both real templates in memory. To show fail-before, I ran this PR's test file against main's Sid-anchoredtfStatements, copied verbatim behind a one-lineparseTFStatementsshim:Action = "ec2:CreateTags"{ Effect = ..., Action = [...], Resource = "*" }:andResource = ["*"]data "aws_iam_policy_document"statement {}blockTestParseTFStatementsRefusesWhatItCannotReadpins the fail-closed paths:Action = local.x,"${var.service}:CreateTags",Resource = var.x, a duplicateActionkey, and a JSON policy in a heredoc (count mismatch). Each case checks the specific error message.Mutation probes of the new scanner. I applied each mutation to a scratch copy and confirmed it turns at least one subtest red:
:separator: 2 red#comments: 2 redactions: 9 redCI-equivalent checks.
go build ./...: okgo vet ./internal/iacfiles/: okgofmt -l internal/iacfiles: cleangolangci-lintv2.10.1run --allow-parallel-runners ./internal/iacfiles/...: 0 issuesgocyclo -over 10 -ignore "_test\.go" internal/iacfiles: silent (both changed files are_test.go; the highest new function is 12, under golangci's test threshold of 15)go test ./internal/mocks/ -count=1: okscripts/setup-git-secrets.shin a freshgit initrepo with an isolated HOME, andgit secrets --scanon both files came back clean. A 12-digit positive control was flagged.scripts/test-git-secrets-allowlist.sh: 42 PASS, 0 FAIL.scripts/gosec-hook.sh): not run, because gosec isn't installed on this machine.Out of scope (not changed here)
NotActiongrants.Action = ["ec2:*"],"ec2:Create*","*", or aNotActionstatement onResource = "*"would still grant tagging without matching the literalec2:CreateTags. This gap exists for all seven policy formats (JSON, CFN and Terraform alike), not only the Sid-less case, so it deserves its own issue.iac/federation/*/terraform/main.tftemplates. This PR doesn't touch them. Open PR fix(iac): bound every Terraform provider constraint below the next major #1203 edits their provider constraints, and the mutants here are applied in memory.internal/iacfiles.terraform/environments/aws/db_pool_budget_test.golooks up theLambdaReservedConcurrencystatement by Sid. That is a named lookup that fails closed if the Sid is renamed, but it would also miss a second, Sid-less grant of the same action.terraform/environments/aws/ci-cd-permissions/policy_guard_test.goalready splits statements without Sid. Neither file is changed.Labels
Could a maintainer please mirror the issue's triage labels on this PR:
impact/internal,type/chore,triaged,priority/p3,severity/low,urgency/eventually,effort/xs.🤖 Generated with Claude Code