Repository navigation
chore(iac/azure): reconcile prod in github_environments with its gating rule - #1162
Conversation
…ng rule The variable's description said a credential must not be minted for an environment that does not exist and is not gated, while its default has included `prod` since LeanerCloud#358. When LeanerCloud#364 was filed no `prod` environment existed. Since 2026-10-04 `dev`, `staging` and `prod` all exist with a deployment branch policy of exactly `main` (LeanerCloud#141, LeanerCloud#2), so `prod` now meets the rule. The description still read as if it did not. State the gating rule explicitly: a name may be added once its environment exists with a main-only branch policy, because the service principal already trusts the main-branch subject and such an environment's credential therefore adds no reach. Say that the default meets it, `prod` included, that no environment has required reviewers, and that widening a policy past `main` (e.g. a tag policy for release deploys) widens the credential's reach. Update the azure-db-* note, whose stated reason (environments not created yet) is no longer true, and point the README's "allow a new environment" step at the rule. Add a mock-provider terraform test pinning the default credential set to exactly dev/staging/prod and the main-branch subject, so a change to who can obtain the service principal has to touch the test and the rule. No resource changes: the default is unchanged, so no bootstrap re-apply is needed. Closes LeanerCloud#364 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configuration
📒 Files selected for processing: 3
✨ Finishing Touches
Comment |
|
Independent review (claude-cc-platform reviewer, cold brief): APPROVE No blocking issues. Live gating, checked read-only with Credential reach: Base drift: clean merge onto current main (tree Nits:
Attacked:
The Azure-side state was not checked (needs credentials). |
|
Independent review (claude-fleet): APPROVE be6a6dd Findings (none blocking):
Attacked:
Verified locally on a
Reviewed by an independent agent with no author context. Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
Local verification (macOS, exact head Gate status:
|
|
Local verification at
|
|
Marked ready and merged by the owner's agent after 2 independent reviews and local verification (see the review and verification comments above, all at |
Root cause
terraform/environments/azure/ci-cd-permissions/variables.tf:github_environmentshas defaulted to["dev", "staging", "prod"]since #358. Its description said a credential must not be minted "for an ungated environment that does not yet exist". When this issue was filed (2026-09-27) noprodenvironment existed, so the default broke the variable's own rule.That has since changed. On 2026-10-04 a maintainer created
dev,stagingandprod(and theaws-*,gcp-db-*,azure-db-*families), each with a deployment branch policy of exactlymain(see the #141 and #2 comments). I checked the live state for this PR:(
devandstagingreturn the same. None of them has required reviewers.)So
prodnow meets the rule, but the description still read as if it did not, and it never stated the rule precisely.Fix
I took the issue's second option and amended the description. Removing
prodwould cause Azureproddeploys (deploy-azure.yml) androllback.yml'srollback-azureto fail with AADSTS70021 after the next bootstrap re-apply.main. The service principal already trustsrepo:<repo>:ref:refs/heads/main(sp.tf,github_main), and only a run onmaincan enter such an environment, so its credential adds no reach. A credential for an environment that does not exist does widen access, because GitHub creates a referenced environment on first use with no protection. The description also gives thegh apicommand to check.prodis not an exception: it has the samemain-only policy and the same service principal asdev/staging. The description also notes that no environment has required reviewers, and that widening a policy pastmain(e.g. av*tag policy for release-triggeredproddeploys) widens this credential's reach too.main-only policies. The note now says that, and leaves adding them to #1648 because that mints new credentials and needs a re-apply.main-only policy first, and points at the rule.tests/github_environments.tftest.hcl(mockazurerm/azureadproviders,command = plan). It pins the default credential set to exactly{dev, staging, prod}, theprodsubject, and thegithub_mainsubject the "no added reach" argument depends on. It also checks thatgithub_repo = ""creates no environment credentials. CI's existing "Test CI/CD trust (immutable ids)" step runs it, since that step runsterraform testfor anyterraform/environments/<cloud>/ci-cd-permissions/testsdirectory.Bootstrap re-apply
None needed.
ci-cd-permissionsis applied manually. This PR changes only a variable's description, a README and a test. The default value and every resource are unchanged, so a plan against live state would show no changes.Verification
All of this is local and offline:
terraformwith-backend=falseand mock providers. No real Azure or GitHub writes, no plan against a real backend, no apply. The only live calls were read-onlygh apiGETs on the repository's environments.About fail-before/pass-after: this issue is a contradiction in documentation, not a behavior bug. The fix changes no plan, so no test can fail before it and pass after it. The new test guards the decision the description records. Mutation probes (default edited temporarily, then reverted):
default["dev", "staging"](dropprod)default_environments_are_the_gated_plain_namesfail (set assertion andprodsubject assertion)["dev", "staging", "prod", "azure-db-prod"](add a name)default_environments_are_the_gated_plain_namesfail (set assertion)Other checks on the touched files:
markdownlint-cli@0.47.0 --config .markdownlint.yamlon the README: clean.trivy config --severity HIGH,CRITICAL --exit-code 1on the module: exit 0.--register-aws, repo.gitallowed) over the three changed files, in a freshgit initrepo with an isolated HOME: clean.scripts/test-git-secrets-allowlist.sh: 42 PASS, 0 FAIL.git diff --check: clean.tflint, which isn't installed here. CI's pre-committerraform_tflinthook covers it.No Go code changed, so I didn't run Go build, vet, golangci-lint, gocyclo or
internal/mocks(they don't apply).Coverage gap: I did not dispatch a run from a feature branch to prove that the
main-only policy refuses it. That would start a real deploy. The "no added reach" argument relies on GitHub enforcing deployment branch policies before it issues the environment-subject token.Out of scope (noted, not changed)
azure-db-{dev,staging,prod}now meet the gating rule. Adding them to the default (#1648) mints three credentials and needs a bootstrap re-apply.proddeploys: these run from a tag ref, which themain-only policy refuses (maintainer note on sec(ci): deployment environments have no protection rules, so environment-bound credentialed jobs run unapproved #141). Adding a tag policy would widen this credential's reach. The new description says so, but the decision is left to the maintainers.sub, so environment bindings change nothing there. That belongs to sec(iac): rename-proof OIDC deploy trust + branch-restricted environments for the split platform repo #2.prodhascan_admins_bypass: true. Repository admins can change these settings anyway, so I didn't treat it as reach this credential adds.Labels
A maintainer please mirror the issue's triage labels onto this PR:
impact/internal,type/chore,triaged,priority/p3,severity/low,urgency/this-quarter,effort/xs.Closes #364
🤖 Generated with Claude Code
Summary by CodeRabbit
main-only deployment branch policy.dev,staging, andprodenvironments and deferred database-migration environments.