Repository navigation
Conversation
GitHub's default OIDC sub and repository claims follow the repository's
current name, so the ci-cd-permissions trusts keyed on var.github_repo
break on a rename and could be claimed by a new repo reusing a freed name.
- AWS role and Azure federated credentials now match subjects of the form
repository_owner_id:<owner_id>:repository_id:<repo_id>:{ref:refs/heads/main,
environment:<name>}.
- GCP maps repository_id / repository_owner_id and conditions on both plus
deploy_ref; the workloadIdentityUser member is the repository_id
principalSet.
- github_repo is replaced by required, validated github_repository_id and
github_repository_owner_id variables; the OIDC resources are no longer
optional, with moved blocks for the former [0] addresses.
- scripts/bootstrap-github-deploy-config.sh (dry run by default) creates
every workflow environment with a main-only deployment branch policy and
sets the matching OIDC sub claim template.
- deploy_trust_guard_test.go fails if any trust references the repo name,
if the script's claim keys drift from the Terraform prefix, or if a
trusted environment is not gated by the script.
Refs #2
Documents the GitHub script, per-cloud terraform plan/apply, the expected plan diff, a test deploy from main, and rollback (including the state mv needed to return to the indexed addresses). Updates the three ci-cd-permissions READMEs to the ID-based subjects. Refs #2
cristim
marked this pull request as draft
September 29, 2026 13:45
This was referenced Oct 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR covers the code half of #2. The CI/CD deploy trust in
terraform/environments/{aws,azure,gcp}/ci-cd-permissions/no longer uses the repository name. It is keyed on the immutablerepository_idandrepository_owner_id. It also adds a reviewed bootstrap script for the GitHub side and a runbook with the apply order and rollback. Nothing was applied: noterraform applywas run and no GitHub setting was changed. The bootstrap step is still pending, so this PR does not close #2.Root cause
The trusts matched
repo:${var.github_repo}:...(AWSsublist and Azure federated credential subjects) andassertion.repository == var.github_repo(GCP). GitHub's defaultsubandrepositoryclaims follow the repository's current name. A rename breaks deploys, and a new repo that reuses a freed name would gain deploy rights.Fix
include_claim_keys):["repository_owner_id","repository_id","context"]. With it, GitHub mintsrepository_owner_id:86753534:repository_id:1391259743:ref:refs/heads/mainand...:environment:<name>.StringEqualstoken.actions.githubusercontent.com:sublist is${local.github_oidc_sub_prefix}:ref:refs/heads/mainplus the same 9 environment subjects as before (dev,staging,prod,aws-fargate-*,aws-db-*).audis unchanged.${prefix}:ref:refs/heads/mainand${prefix}:environment:<each github_environments>.attribute.repository_idandattribute.repository_owner_id. The condition isassertion.repository_owner_id == '<owner>' && assertion.repository_id == '<id>' && assertion.ref == '<deploy_ref>'.workloadIdentityUseris granted onprincipalSet://.../attribute.repository_id/<id>.github_repois replaced by the required variablesgithub_repository_idandgithub_repository_owner_id. Both have no default and must match^[1-9][0-9]*$.terraform.tfvars.exampledocuments their values. Because trust now requires the IDs, the OIDC resources are no longer optional (the old "setgithub_repoempty to skip" mode is gone).movedblocks cover the former[0]addresses. After the apply, no trust names any repository, so the old monorepo-name entries go away.scripts/bootstrap-github-deploy-config.shruns as a dry run by default, and--applymakes the changes. It is idempotent. It creates all 15 workflow environments (dev,staging,prod, andaws-fargate-*,aws-db-*,gcp-db-*,azure-db-*for each ofdev/staging/prod). Each getscustom_branch_policies=truelimited tomain, and existing reviewers and wait timers are kept. The script stops with an error if an environment admits any other ref. It also sets the sub template.docs/deploy-trust-bootstrap.mdcovers the apply order: record state, run the GitHub script, then plan and apply each cloud's module (with the expected plan diff), then a test deploy frommain. Its rollback section includes thestate mvcommands needed to return to the indexed addresses. The three ci-cd-permissions READMEs and 7 workflow comments are updated to the new subject format.Human steps remaining (#2)
Follow
docs/deploy-trust-bootstrap.md. In each module'sterraform.tfvars, setgithub_repository_id = "1391259743"andgithub_repository_owner_id = "86753534"and deletegithub_repo. Protectmainfirst (#154). Copying the Actions secrets and variables (step 4 of #2) is not in this PR.Regression test
terraform/environments/deploy_trust_guard_test.gochecks three things:deploy_ref;SUB_CLAIM_KEYSmatches the Terraform prefix, and every environment a cloud trusts is gated by the script.Evidence that the tests have teeth:
.tffiles stashed, all 3 tests fail.stagingoraws-db-prodfrom the script fails the environment check.Verification
go test ./terraform/environments/(GOTOOLCHAIN=go1.26.6): 3 passed.go vetandgofmtare clean.terraform fmt -check -recursive: clean.terraform init -backend=false && terraform validatepasses on aws, azure and gcp.tflint --config=.tflint.hclexits 0 on all 3 modules.terraform consoleconfirms the variable validation rejectsLeanerCloud/xand0123and accepts1391259743.shellcheckis clean on the script. I ran the script only against a stubgh, never against GitHub:--applymade the expected PUT/POST set and kept an existing reviewer and wait timer;feature/*branch policy stops the script with exit 1.pre-commit runon the changed files passes, withSKIP=actionlintbecause the Docker daemon on this host is hung. The nativeactionlintpasses on all workflows.Refs #2