Repository navigation
test(api): keep internal/api tests off the network and fail the run if one reaches it - #1121
Conversation
…f one reaches it Two internal/api tests reached real AWS endpoints: the in-scope RI utilization permission test built a real Cost Explorer client (anonymous CONNECT to ce.us-east-1.amazonaws.com), and the in-scope exchange quote test went through exchange.GetExchangeQuote, which loads its own AWS config and so probed IMDS. Two more quote tests only stayed offline because no AWS region was configured; with AWS_REGION and credentials set they called EC2. Add an exchangeQuoteFn seam on Handler (mirroring executeExchangeFn), inject stubs into those tests and the existing reshape client factories into the utilization test, and tighten their assertions to prove the stubbed call was reached. Add a TestMain no-network guard for the package, extending the one from LeanerCloud#852: it strips ambient AWS_* configuration, routes proxy-aware clients (including the IMDS client) into a recording local proxy, swaps net.DefaultResolver for one that records looked-up names, and fails the run listing every blocked target. A self-test pins the guard. Closes LeanerCloud#855 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 7 billable files and costs up to $1.75. Or wait 58 minutes for your next included review. View limit details
Comment |
|
VERDICT: APPROVE b495b3a Reviewed head b495b3a: 1 commit, 371 changed lines, under the 400 cap. The base 8caed39 is 41 commits behind. Findings (none blocking):
Attacked:
|
|
Independent review 2 (claude-fleet, cold): APPROVE b495b3a Findings (none blocking):
Against #855: this fixes the root cause.
Attacked:
Verified locally, on
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
Local verification at
|
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request, after 2 independent reviews and local verification (see the review and verification comments above, all at |
Root cause
Two
internal/apiunit tests built real AWS clients:TestPerAccountPerms_GetRIUtilization_ScopedWithinAllowedProceedsToAWSsealedawsCfgOncewith an emptyaws.Configand injected no client factories.getRIUtilizationtherefore built the real Cost Explorer client and sent an anonymous request toce.us-east-1.amazonaws.com(3 attempts).TestGetExchangeQuote_InScopeDeploymentAccountPassesGateseededh.awsCfgwith failing credentials, butgetExchangeQuotecallsexchange.GetExchangeQuote, which ignoresh.awsCfgand runs its ownLoadDefaultConfig. The default credential chain then probed IMDS (12 requests, about 13 s).Two more quote tests (
TestGetExchangeQuote_ValidOfferingIDPassesFormat,TestGetExchangeQuote_EmptyRegionResolvesFromSDK) also went through the realexchange.GetExchangeQuote. They only stayed offline because no AWS region was set (or because of an upstreamtarget_countcheck). WithAWS_REGIONand credentials in the environment,ValidOfferingIDPassesFormatsentGetReservedInstancesExchangeQuotetoec2.eu-west-1.amazonaws.com(shown below).Fix
Production seam (no behavior change): a new
Handler.exchangeQuoteFnfield and anawsExchangeQuoter()accessor. They mirror the existingexecuteExchangeFn/awsExchangeExecutor().getExchangeQuotecalls through the accessor, which returnsexchange.GetExchangeQuotewhen the field is nil, so production is unchanged.Tests:
reshapeRecsFactory/reshapeEC2Factoryseams using the package's counting stubs. It asserts that the Cost Explorer fetch was reached and checks the rows returned.exchangeQuoteFnstub. Their assertions are stricter than before: the out-of-scope test must not reach the quote, and the in-scope test must reach it once with the resolved region. The old softif err != nil { NotContains }checks are replaced with hard assertions on the call and the region.Guard (
internal/api/main_nonet_test.go): aTestMainthat extends the feat(ladder): prefetch commitment visibility for the tranche reconciler #852 recording-proxy guard. Before any test runs, it:AWS_*variable and pointsAWS_CONFIG_FILE/AWS_SHARED_CREDENTIALS_FILEat the null device. Local runs then resolve the same empty identity as CI and can never use a developer's real credentials.net.DefaultResolverwith a Go resolver whose name server is an in-memory pipe. A direct dial to a hostname that isn't in the hosts file then fails locally, and the looked-up name is recorded.If anything was blocked, the run fails at the end and lists every blocked target. Each one is also printed as it happens, so with
-vit appears next to the test that caused it.TestNoNetworkGuard_BlocksOutboundCallscovers the guard itself (an HTTPS call and a DNS lookup must both be recorded).net.DefaultResolver.Closes #855
Verification
All of this is unit, mock and fixture evidence. No real cloud calls were made: every run had
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/nulland no real credentials. This machine's~/.aws/configdefault profile is invalid, so the variables were also needed locally. The guard now sets them itself.Fail-before (
origin/maintest files plus only the new guard file):The latent EC2 call, shown on
origin/maintests with the guard's env isolation temporarily disabled, using faketestcredentials andAWS_REGION=eu-west-1:Pass-after:
The integration run used real testcontainers Postgres on local Docker. It shows that the guard's proxy env and resolver swap don't interfere with the Docker client, Ryuk or pgx, and nothing was blocked. The only guard output in either run is the self-test's own deliberate entries.
Mutation probe: reverting
getExchangeQuoteto callexchange.GetExchangeQuotedirectly fails the three in-scope quote tests and the guard (36 IMDS requests blocked).Other checks:
go build ./...,go vet ./internal/api/,go vet -tags integration ./internal/api/golangci-lintv2.10.1run --build-tags integration ./internal/api/: 0 issuesgocyclo -over 10 -ignore "_test\.go"on the changed non-test files: silentgo test ./internal/mocks/ -count=1: okscripts/gosec-hook.shon the changed files: clean--register-awsplusscripts/setup-git-secrets.shpatterns) on the changed files, in a throwaway repo with an isolated HOME: clean.scripts/test-git-secrets-allowlist.sh: 42 PASS, 0 FAILgofmt,git diff --check: cleanOut of scope / notes
exchange.GetExchangeQuote) is unchanged and isn't exercised against AWS here.internal/purchase(feat(ladder): prefetch commitment visibility for the tranche reconciler #852) and nowinternal/apido. I didn't audit other packages for network access.ri_exchange_carveout_test.gostill relies ont.Setenv("AWS_EC2_METADATA_DISABLED", "true")as defense in depth. I left it alone because it doesn't reach the network today; the guard would catch it if it did.Maintainer: please mirror the issue's triage labels on this PR:
priority/p2,severity/medium,urgency/this-quarter,impact/internal,effort/m,type/bug,triaged.🤖 Generated with Claude Code