Repository navigation
test(api): RI-exchange carve-out test reaches a live AWS STS/IMDS call under mutation #175
Description
Summary
TestExecuteExchange_PlainAdminIsRefused (internal/api/ri_exchange_carveout_test.go, added in PR LeanerCloud/cloud-commitments-cli#1758) is fast and offline in the state we ship. But under mutation — with {ActionExecute, ResourceRIExchange} removed from adminCarvedOuts, which is exactly how the test is verified — execution proceeds past the permission gate and attempts a live AWS call:
failed to resolve cloud account scope: resolve source aws account for reshape scope:
sts get-caller-identity: operation error STS: GetCallerIdentity, exceeded maximum number
of attempts, 3, get identity: get credentials: failed to refresh cached credentials,
no EC2 IMDS role found, operation error ec2imds: GetMetadata, exceeded maximum number of
attempts, 3, request send failed, Get "http://169.254.169.254/latest/meta-data/iam/
security-credentials/": dial tcp 169.254.169.254:80: connect: host is down
Unmutated the carve-out refuses at the gate in ~1.3s and no call is made. The exposure exists only while someone re-runs the mutation matrix — but that is a routine verification step for this test, not an exotic one.
Why it is worth fixing
- Correctness, not just slowness. The 9.7s figure is a network timeout on a machine with no credentials. On a machine that has AWS credentials in the environment, that path resolves a live account identity. Nobody re-running a mutation matrix should have to think about which credentials are loaded.
- It is the mirror of a positive we recorded recently. PR fix(cli): apply --max-instances once run-wide, not per service and region cloud-commitments-cli#1725's guard hoist was called out in review specifically because it removed a live
DescribeReservedDBInstancesfrom the unit suite. This is the same class, introduced rather than removed. - It makes the mutation matrix environment-dependent, which is the property that makes a verification step untrustworthy.
Mechanism NOT traced — deliberately
I established where it is not and stopped there rather than guessing:
- The failure is in cloud-account scope resolution, which happens upstream of the
executeExchangeFnseam atinternal/api/handler.go:126-130("the RI exchange execution function injected by tests"). Injecting that seam alone would not prevent the call. - It is not reached through
h.config, so stubbingmockStorecannot block it either — which is the obvious first guess and is wrong.
I have not read what constructs the STS client, so I am not proposing a mechanism. That is the expensive half and can be done fresh.
Suggested direction (unvalidated)
Either an injectable scope resolver on Handler, or a fail-fast when no credential source is configured under test, so the path errors immediately and locally instead of attempting network I/O. Whoever picks this up should trace the constructor first; the above is a starting point, not a design.
Reproduction
- Remove
{ActionExecute, ResourceRIExchange}fromadminCarvedOutsininternal/auth/types.go. go test -count=1 -run '^TestExecuteExchange_PlainAdminIsRefused$' ./internal/api/- Observe the STS/IMDS error above and the ~9.7s runtime (vs ~1.3s unmutated).
Found while mutation-verifying PR LeanerCloud/cloud-commitments-cli#1758 (issue LeanerCloud/cloud-commitments-cli#1644).
Activity
test(api): assert the 403 identity, not error-string tokens, in the e…
sec(auth): carve execute:ri-exchange out of admin:* and seed the gran…