Repository navigation
fix(scripts): add OIDCIssuerHost to generate-federation-iac and test for drift - #1256
Conversation
…for drift iacData in scripts/generate-federation-iac.go lacked OIDCIssuerHost, which aws-cfn-deploy.sh.tmpl references, so --format bundle failed for every AWS target with a non-AWS source. Add the field and derive it from OIDCIssuerURL the same way the server does. The doc comment asked people to keep iacData in sync with the server's federationIaCData, and nothing enforced it. New tests read both structs with go/parser and every template with text/template: they fail when the two structs stop having the same fields, or when any template references a field iacData lacks. Doing it from source keeps the script decoupled from internal/api. The doc comment now points at these tests. Also drop the stale "iacData is missing fields" rationale from three tests. TestGenerator_CrossAccountDoesNotRequireSubjectClaim now asserts a successful render instead of only checking stderr (audit A14-028). Closes LeanerCloud#153 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 4 billable files and costs up to $1.00. Or wait 56 minutes for your next included review. View limit details
Comment |
|
Heads-up on an interaction with a queued branch: |
Brings in main's CI fixes (LeanerCloud#1236, LeanerCloud#1252) and CI changes since, so this PR gets a fresh CI run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rB4. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
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 |
Two textual conflicts, both resolved by keeping both sides: - ci.yml: main added an init-backend.sh self-test step to the same job this branch appends the Function URL reader self-tests to, plus a new tfstate-concurrency-group job after it. Both steps stay in the job, and main's job follows. - cloud-run/.terraform.lock.hcl: main's "~> 5.45" constraint with the union of both sides' h1: hashes for google 5.45.2. LeanerCloud#1256 (merged on main) added OIDCIssuerHost to the standalone generator; the next commit drops it again to match this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LeanerCloud#1256 added OIDCIssuerHost to iacData because aws-cfn-deploy.sh.tmpl passed it at the time. This branch removes the parameter from the CloudFormation template and the deploy script (the host is derived from OIDCIssuerURL), so the field is unused again and the drift test that compares iacData with federationIaCData fails until it goes. The bundle test now checks that the rendered deploy script passes OIDCIssuerURL and never OIDCIssuerHost, since CloudFormation rejects an override for an undeclared parameter. The field walker's control uses OIDCIssuerURL instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rst, read the dev Function URL from state (#1312) * chore(iac): derive the CFN issuer host, create the GCP signing key first, read the dev Function URL from state Three low findings from the 2026-09-02 audit. A13b-014: the aws-target CloudFormation template took the trust policy's condition-key host as a second parameter, OIDCIssuerHost, that the operator had to keep equal to OIDCIssuerURL minus https://. The host is now derived with !Select [1, !Split ["https://", !Ref OIDCIssuerURL]], the same value as the Terraform module's local.oidc_condition_host, and the server no longer passes the parameter in the bundle's params JSON or deploy script (CFN would reject an undeclared parameter). A13c-024: the Cloud Run module's OIDC signing key now has create_before_destroy, matching its AWS twin, so a change that forces a new key cannot destroy the live one first. destroy_scheduled_duration becomes an input whose default keeps every deployed key unchanged (the provider marks it ForceNew). prevent_destroy cannot be an input (Terraform requires a literal) and staging cleanup runs terraform destroy, so the redundant `prevent_destroy = false` line is replaced by a comment saying so. A13c-017: github-dev.tfvars no longer pins the dev Lambda Function URL host. The deploy and rollback workflows read the previous apply's lambda_function_url output (scripts/read-lambda-function-url.sh) and pass it as TF_VAR_lambda_deployed_function_url; compute.tf appends its origin to the CORS allowlist. A post-apply step warns when the apply changed the URL. Refs #324 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(iac): allowlist the mocked caller-identity account ID in the CORS tftest The combined-tree git-secrets run flagged terraform/environments/aws/tests/lambda_cors_origins.tftest.hcl:10, a bare account_id = "123456789012" in the mocked aws_caller_identity. Add an anchored whole-line .gitallowed entry for that one line (bracketed digits so the entry does not match itself), placed before the other terraform/ entries so it does not collide with #1255's entry for its own tftest. Refs #324 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge origin/main into fix/issue-324 Bring the branch up to date with main. Conflicted files: - .github/workflows/ci.yml: this branch appended a "Run Function URL reader self-tests" step to the aws-tfstate-platform-key job; main (#1112) added a new rollback-ecr-registry job right after that job. Kept both: the step stays at the end of aws-tfstate-platform-key and main's job follows it unchanged. rollback.yml merged cleanly: main's rewritten "Verify image exists" steps and this branch's TF_VAR_lambda_deployed_function_url export touch different steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge origin/main into fix/issue-324 Two textual conflicts, both resolved by keeping both sides: - ci.yml: main added an init-backend.sh self-test step to the same job this branch appends the Function URL reader self-tests to, plus a new tfstate-concurrency-group job after it. Both steps stay in the job, and main's job follows. - cloud-run/.terraform.lock.hcl: main's "~> 5.45" constraint with the union of both sides' h1: hashes for google 5.45.2. #1256 (merged on main) added OIDCIssuerHost to the standalone generator; the next commit drops it again to match this branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(scripts): drop OIDCIssuerHost from the standalone IaC generator #1256 added OIDCIssuerHost to iacData because aws-cfn-deploy.sh.tmpl passed it at the time. This branch removes the parameter from the CloudFormation template and the deploy script (the host is derived from OIDCIssuerURL), so the field is unused again and the drift test that compares iacData with federationIaCData fails until it goes. The bundle test now checks that the rendered deploy script passes OIDCIssuerURL and never OIDCIssuerHost, since CloudFormation rejects an override for an undeclared parameter. The field walker's control uses OIDCIssuerURL instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(iac): note the key ring order and the URL reader's failure mode Review follow-ups on #1312. create_before_destroy on the signing key also applies to the key ring it depends on, and the Function URL reader treats any terraform output failure as "no URL". Both are intended, but neither was written down where the code is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(api): pass t to federationHandler in the CFN bundle test #1223 on main made federationHandler take *testing.T so it can register AssertExpectations on its mock. This branch's CFN bundle parameter test still called it with no arguments, so internal/api stopped compiling after the merge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Merge origin/main into fix/issue-324 Picks up #1467, which also edits scripts/generate-federation-iac.go, so the generator tests are re-run on the merged tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root cause
scripts/generate-federation-iac.gorenders the same templates as the server'sGET /api/federation/iac, but from its own copy of the template data struct.iacData's doc comment said "Keep in sync with any template variable changes", and nothing enforced that. By the time of this PR, #1710 had addedSourceAccountID,CUDlyAPIURLandContactEmail. One field was still missing:OIDCIssuerHost, whichaws-cfn-deploy.sh.tmplreferences. So--format bundlestill failed for every AWS target with a non-AWS source. Reproduced on main (a14dc8f):The existing end-to-end tests didn't catch this. They cover tfvars for every combination but bundle only for Azure targets, and
TestGenerator_ShellTemplatesEscapeValuesswaps in a probe foraws-cfn-deploy.sh.tmplprecisely because the real template couldn't be rendered.Fix
iacDatagainsOIDCIssuerHost.populateDataderives it fromOIDCIssuerURLwith the samestrings.TrimPrefix(..., "https://")thatbuildGenericIaCDatauses on the server.escapeDatawalks every string field by reflection, so the new field is shell-escaped in.shoutput without any extra code.scripts/generate_federation_iac_drift_test.go):TestIaCData_MatchesServerTemplateDatareadsiacDataandfederationIaCDatafrom source withgo/parserand requires the same field set.TestIaCData_CoversEveryTemplateFieldparses everyinternal/iacfiles/templates/*.tmplwithtext/template, walks the parse tree, and fails if a template references a fieldiacDatalacks. This catches a new template field before anyone runs the script. If a template ever useswithorrange, the test fails with "extend templateFieldRefs" rather than guessing what dot means.TestIaCData_FieldWalkerSeesKnownFieldsis a positive control, so a walker that finds nothing can't make the two tests above pass vacuously.TestGenerator_AWSWIFBundleRendersDeployScriptruns the real--format bundleforaws←gcpandaws←azureand checks theOIDCIssuerHost=value incloudformation/deploy-cfn.sh. It only reads the rendered script and never runs it, since running it would call the AWS CLI.TestGenerator_CrossAccountDoesNotRequireSubjectClaimnow passes--source-account-id, asserts exit 0, and checks that the target account ID is rendered. Before, it only checked stderr, so it passed whatever the aws→aws path returned.TestGenerator_AcceptsValidOIDCSubjectClaimandprobeDeployScript, which said the tfvars and deploy-script paths could not render.Why a source-level check instead of sharing one type
The issue body asks not to couple the script's struct to
internal/api. Importinginternal/api(or moving the type intointernal/iacfiles) would make a//go:build ignoredev tool depend on a large server package. The drift tests read both files as source and import neither, so the script stays decoupled while the drift still fails CI. cristim's follow-up comment suggested this pair of checks (field-name comparison plus every-template coverage).Closes #153
cristim's follow-ups on the issue (from cli#1691)
SourceAccountID/ full field list. In scope and fixed.SourceAccountID,CUDlyAPIURLandContactEmailand their flags were already on main via #1710. This PR adds the remainingOIDCIssuerHost, derived as the server does, plus the drift tests above.shellEscapedoes not escape}. Out of scope and still present on main.internal/api/handler_federation.goshellEscape(and the script's copy) still leave}alone. 8 templates still put{{.ContactEmail}}inside"${CUDLY_CONTACT_EMAIL:-...}":aws-cfn-deploy,aws-cross-account-cfn-deploy,aws-cross-account-cli,aws-wif-cli,azure-wif-cli,azure-wif-deploy,gcp-sa-impersonation-cli,gcp-wif-cli. Only the bicep deploy script was fixed, in fix(iac): keep '}' and JSON metacharacters in the Bicep/ARM contact email intact (#963 follow-ups) #992 (Follow-ups from #963 review: '}' and JSON metacharacters in the Bicep/ARM contact email #991). This is a server-side escaping change, not generator/handler drift, and it touches the server's CLI templates, so it needs its own issue. As the issue comment notes, it can't run commands, because$,`,"and\are all escaped.azure-wif-bicep-deploy.sh.tmplrender now goes throughshellEscapeData(sec(api): shell-escape the Bicep/ARM deploy script render (#958) #963), and the.auto.tfvarsrender goes throughhclEscapeData.Out of scope / noted
scripts/generate-federation-iac.goreports 11 issues on lines this PR does not touch, e.g.recognise(misspell), gosec G306 on0o644, revive error-strings. CI doesn't lint this file because it carries//go:build ignore, and none of the findings are on changed lines.Verification
Run on this branch with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=trueand Go 1.26.9. This is unit and subprocess evidence: the tests run the compiled script against the repo's real templates. No cloud calls.Fail before the fix (new tests, script unchanged):
Pass after:
Mutation probes. Each was applied temporarily and reverted, and each one fails the tests:
OIDCIssuerHost = TrimPrefix(...)line, keep the fieldTestGenerator_AWSWIFBundleRendersDeployScript(gcp, azure){{.NewField}}togcp-wif.tfvars.tmplTestIaCData_CoversEveryTemplateField:NewField (used by gcp-wif.tfvars.tmpl)federationIaCDataonlyTestIaCData_MatchesServerTemplateData{{with .AccountName}}block to a templateTestIaCData_CoversEveryTemplateField: "*parse.WithNode ... extend templateFieldRefs"Other checks:
go build ./...,go vet ./...,go vet -tags integration ./...,go vet scripts/generate-federation-iac.go: all clean.golangci-lint run --allow-parallel-runners ./scripts/...(v2.10.1): 0 issues.gocyclo -over 10 -ignore "_test\.go" scripts/generate-federation-iac.go: no output.go test ./internal/mocks/ -count=1: ok.Combined-tree verification. This branch was tested together with six other queued branches on a local, never-pushed tree:
queue/platform@77d116c6= origin/maina14dc8f2plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration, andgocyclo -over 10is silent on the changed non-test files. These ran atb61a9c30, which differs from77d116c6only by one.gitallowedline on fix/issue-1233.-tags integration -count=1):cmd/cleanup-lambda,internal/analytics,internal/auth,internal/database/postgres/migrationsandinternal/purchase.internal/apipasses, except two tests whose Postgres container didn't start, or whose migrations hit a timeout, under machine load. Rerun alone on the same tree, both pass:TestDeleteManagedPlanByExecutionStatusandTestNotifiedRow_RescheduleDoesNotOverwriteConcurrentCancel.internal/configpasses exceptTestPostgresStoreDB_ResolveStaleRIExchange_ValidatesCloudCheck, which fails the same way on unmodified main (fixed by fix(config): compare resolve checked_at at the database's microsecond precision #1252).TESTCONTAINERS_RYUK_DISABLED=trueto avoid a testcontainers reaper race under load.scripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 137 PASS, 0 FAIL.Labels
A maintainer, please mirror the issue's triage labels onto this PR:
impact/internal,triaged,priority/p3,severity/low,urgency/eventually,effort/xs,type/bug.🤖 Generated with Claude Code