Repository navigation
chore(scripts): generate-federation-iac.go iacData has drifted out of sync with the templates it renders #153
Description
scripts/generate-federation-iac.go is a standalone dev tool for rendering
the same templates the runtime API (GET /api/federation/iac,
internal/api/handler_federation.go) renders. Its own doc comment says:
iacDatamirrorsinternal/api/handler_federation.go:federationIaCData.
Keep in sync with any template variable changes.
It has not been kept in sync. iacData is missing several fields that
internal/iacfiles/templates/aws-wif.tfvars.tmpl and
aws-cfn-deploy.sh.tmpl already reference:
CUDlyAPIURL— referenced by the{{if .CUDlyAPIURL}}auto-registration
block at the bottom ofaws-wif.tfvars.tmpl(and other tfvars templates).ContactEmail— referenced inside that same block.OIDCIssuerHost— referenced byaws-cfn-deploy.sh.tmpl's
--parameter-overrides.
Reproduce (pre-existing on main, unrelated to any in-flight PR):
go run scripts/generate-federation-iac.go \
--target aws --source gcp \
--account-name "prod-aws" --account-id "123456789012" \
--output -
Error: render internal/iacfiles/templates/aws-wif.tfvars.tmpl: template: iac:24:19: executing "iac" at <.CUDlyAPIURL>: can't evaluate field CUDlyAPIURL in type main.iacData
The default (tfvars) and --format bundle output for --target aws (any
non-aws --source) are currently unusable — text/template errors out
executing the struct field reference. --format cf-params still works
(that template doesn't reference the missing fields).
Not a security issue — this is a developer-facing tool
(//go:build ignore, run via go run, never reachable from customer
traffic), and its output is unusable rather than silently wrong. Filing so
it doesn't stay silently broken.
Suggested fix
Superseded in part by LeanerCloud/cloud-commitments-cli#1709. That issue tracks the missing iacData
fields as a current breakage on main, with a wider field list
(SourceAccountID as well) and an end-to-end render test. Add the fields
there, not here.
What remains for this issue once LeanerCloud/cloud-commitments-cli#1709 lands: fix the doc comment. "Keep in
sync with any template variable changes" claims an invariant nothing
enforces, which is how the drift went unnoticed in the first place. Say what
is true instead, and point at whatever coverage LeanerCloud/cloud-commitments-cli#1709 adds as the thing that
actually holds the two in step.
Not doing:
- Flags for the fields (
--cudly-api-url,--contact-email).
--account-name/--account-idexist because the output is meaningless
without them; these are optional in every template that references them.
Add a flag when someone needs to set one. - A reflection test over field names, or unifying the two structs. fix(scripts): generate-federation-iac.go fails on all tfvars paths — iacData missing three template fields cloud-commitments-cli#1709
covers the drift by rendering every--target/--sourcecombination and
asserting it succeeds, which catches a missing field without coupling the
tool's struct tointernal/api's.
Remedy simplified (2026-08-03): the two new flags and the reflection sync
test were dropped, leaving the doc-comment correction. Note also corrected
the same day: an earlier revision of this section claimed no test could cover
the script because it is //go:build ignore. That is true of a test that
imports the struct, but LeanerCloud/cloud-commitments-cli#1709 shows an end-to-end test that executes
the script covers the same drift, so the coverage half of the original
remedy was right and this section was wrong to drop it outright.
Found while working on LeanerCloud/cloud-commitments-cli#1640 (internal/iacfiles/templates/aws-wif.tfvars.tmpl
now also references {{.OIDCSubjectClaim}}, which I added to iacData in
that PR — but the pre-existing missing fields above are unrelated to that
change and out of its scope).
Findings from the 2026-09-02 codebase audit
Added by an automated audit of 3c0f8ac94048a2c36fce5ccddee54e6c4849a5cd (tip of origin/main). Each item below was reported by one reviewer and independently confirmed by a second that did not write it. Full report: docs/audits/codebase-audit-2026-09-02.md.
A14-028 (low)
This drift is also load-bearing on a test's rationale, so the two should be closed together. TestGenerator_CrossAccountDoesNotRequireSubjectClaim (scripts/generate_federation_iac_test.go:434-446) asserts only on stderr, with a comment explaining that the invocation "currently fails for an unrelated pre-existing reason: iacData is missing the SourceAccountID / CUDlyAPIURL / ContactEmail / OIDCIssuerHost fields ... so no tfvars path of this script renders on main today". Three of those four now exist (SourceAccountID at generate-federation-iac.go:119, CUDlyAPIURL and ContactEmail at :141-142) and generate-federation-iac_test.go:40 renders tfvars successfully, so the justification is stale and the test currently passes whatever exit code the aws-to-aws path returns, including a regression that breaks it outright. Once the remaining OIDCIssuerHost gap is closed here, the test should assert res.exitCode == 0 and lose the rationale. (audit finding A14-028)
Activity
cristim commentedon Aug 3, 2026
Three follow-ups found while implementing the CodeRabbit fixes on LeanerCloud/cloud-commitments-cli#1691 (which closes LeanerCloud/cloud-commitments-cli#1640). All are pre-existing on
origin/mainand all were deliberately left out of LeanerCloud/cloud-commitments-cli#1691 to keep that PR scoped to the--oidc-subject-claimpath. Filing here since this issue already tracks the drift betweenscripts/generate-federation-iac.goand the templates.1.
iacDatais also missingSourceAccountID, so theaws -> awscross-account path is broken tooThe issue body lists
CUDlyAPIURL,ContactEmailandOIDCIssuerHost.SourceAccountIDis missing as well:internal/iacfiles/templates/aws-cross-account.tfvars.tmplreferences it, so the cross-account combination fails the same way the WIF one does.Reproduced against
origin/main's copy of the script (34d85bc1b):Full set of fields the templates reference but
iacDatalacks:CUDlyAPIURL,ContactEmail,SourceAccountID,OIDCIssuerHost. Net effect: every.tfvarspath of this script is unusable, plus--format bundlefor all targets.--format cf-paramsis the only working mode.This is why LeanerCloud/cloud-commitments-cli#1691's
TestGenerator_CrossAccountDoesNotRequireSubjectClaimasserts on stderr rather than onexitCode == 0; there is a comment in that test pointing back here.2.
shellEscapedoes not escape}internal/api/handler_federation.go:399It covers$but not</code>}<code>. Several CLI templates interpolate values inside a $ {VAR:-{{.Field}}}
\ "default, e.g.aws-wif-cli.sh.tmpl:21and:139,aws-cfn-deploy.sh.tmpl:56. A value containing}terminates the parameter expansion early, so the remainder of the value lands in the script as bare shell tokens rather than as part of the string. Escaping$alone does not prevent that, because the}` is structural to the surrounding expansion, not to the value.Not currently exploitable: every field that reaches those expansions is built by
buildGenericIaCDatafrom the validatedtarget/source/config URL, or bypopulateSourceAccountIDfrom the database. It is a latent gap in a helper whose stated contract is "safe use inside a double-quoted bash argument", which}breaks.3. Two render paths that emit shell/HCL bypass escaping entirely
shellEscapeDatais applied at only two call sites (handler_federation.go:274for single-filecli, and:640forcloudformation/deploy-cfn.sh). Two other renders take the raw struct:handler_federation.go:532renderstemplates/azure-wif-bicep-deploy.sh.tmplwith rawdata. This is a shell script written into the Azure bicep/ARM zip, and it is the one deploy script in the codebase that does not get theshellEscapeDatatreatment its siblings get. Most likely an oversight rather than a decision.handler_federation.go:592renders the.auto.tfvarstemplates with rawdata. Here escaping is not the right fix: shell escaping is wrong for HCL. HCL string values need their own treatment (",\, and${/%{interpolation sequences), or the values need validating at the boundary the way sec(iac/aws): require OIDC subject claim in the federation bundle generator cloud-commitments-cli#1691 now validates--oidc-subject-claim.Same caveat as LeanerCloud/cloud-commitments-cli#2: the fields are not attacker-controlled from the request today, so this is hardening, not an open hole.
Suggested shape for whoever picks this up
iacDataand wire them from flags (--cudly-api-url,--contact-email,--source-account-id); deriveOIDCIssuerHostfromOIDCIssuerURLas the server does.iacDataandfederationIaCDatastay field-compatible (reflection over field names is enough) so this cannot drift again silently. Better still, render every template ininternal/iacfiles/templateswithOption("missingkey=error")againstiacDatain a test, which catches a missing field the moment a template starts using it.shellEscapeData(with}added toshellEscape), HCL templates get an HCL-safe path or boundary validation, JSON templates go throughencoding/jsonrather than string interpolation.sec(scripts): validate --oidc-subject-claim before any rendering
sec(iac/aws): require OIDC subject claim in the federation bundle gen…