Repository navigation
docs(ci): replace stale AZURE_CREDENTIALS setup with the OIDC secrets - #1226
Conversation
The workflows README still told operators to create an --sdk-auth service principal and store it as AZURE_CREDENTIALS, which no workflow reads. Every azure/login step now uses AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_SUBSCRIPTION_ID secrets with federated identity. Document those, the issuer/audience/subject the federated credentials need, and which GitHub Environments the Azure jobs bind (including the azure-db-* gap the bootstrap module does not cover yet). Closes LeanerCloud#28 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 1 billable file and costs up to $0.25. Or wait 57 minutes for your next included review. View limit details
Comment |
Widens the LeanerCloud#28 scrub to the A14-006 audit finding. The workflows README still told operators to store AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY and a downloaded GCP_SA_KEY, and to grant roles/run.admin by hand. No workflow reads those: every configure-aws-credentials step uses vars.AWS_ROLE_TO_ASSUME and every google-github-actions/auth step uses vars.GCP_WORKLOAD_IDENTITY_PROVIDER and vars.GCP_SERVICE_ACCOUNT. Document those variables, the AWS role trust (issuer, sts.amazonaws.com audience, the role.tf subject list and which jobs present each environment), the GCP WIF attribute condition, and that the deploy SA's roles come from the bootstrap module's deploy_roles list. Note the repo's immutable OIDC subject prefix, which the AWS and Azure subjects must use, and correct the Azure subject accordingly. Also fix stale Azure deployment details: add ADMIN_EMAIL, TF_BACKEND_AZURE and optional ALERT_EMAILS, the push-to-main trigger, and the westus2 AZURE_LOCATION default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review 2 (claude-fleet, cold): APPROVE fcf7915 Every trust claim in the new text matches the workflows and IaC, in both the head and the merged tree. None is weaker than the IaC, and nothing tells anyone to create a long-lived key. Findings (none blocking):
Claims checked against source:
Verified locally:
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
VERDICT: APPROVE fcf7915 Docs-only change to Findings (none blocking):
Attacked:
|
|
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: none Reviewer: rB3. 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: 2 approving independent review(s) on |
Root cause
.github/workflows/README.mdwas not updated when CI moved to OIDC. Its setup guide still told operators to create and store a long-lived credential for each of the three clouds, and no workflow reads any of them:az ad sp create-for-rbac --sdk-authJSON stored asAZURE_CREDENTIALS.AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY.GCP_SA_KEY, plus a manual project-wideroles/run.admingrant. The troubleshooting section repeated the grant.So following the guide only creates the long-lived, exfiltratable credentials the OIDC design is meant to avoid. This covers the original issue and the A14-006 audit addendum.
What the workflows actually read (current main)
.github/workflows/*.yml)azure/login(deploy-azure x2, rollback, cleanup-staging, database-migration)secrets.AZURE_CLIENT_ID,secrets.AZURE_TENANT_ID,secrets.AZURE_SUBSCRIPTION_IDaws-actions/configure-aws-credentials(deploy-aws-lambda x2, deploy-aws-fargate, rollback x2, cleanup-staging x2, destroy-fargate-dev, database-migration)vars.AWS_ROLE_TO_ASSUME,vars.AWS_REGIONgoogle-github-actions/auth(deploy-gcp, rollback, cleanup-staging, database-migration)vars.GCP_WORKLOAD_IDENTITY_PROVIDER,vars.GCP_SERVICE_ACCOUNTNo workflow reads a static key, so I removed all of those instructions. None needed to stay.
Fix (docs only, one file, two commits)
ADMIN_EMAILandTF_BACKEND_AWS, plus optionalALERT_EMAILS,FROM_EMAILandDASHBOARD_URL. Added theAWS_ROLE_TO_ASSUMEvariable.GCP_SA_KEYis replaced byADMIN_EMAILandTF_BACKEND_GCP, plus optionalALERT_EMAILS. Added theGCP_WORKLOAD_IDENTITY_PROVIDERandGCP_SERVICE_ACCOUNTvariables.AZURE_CREDENTIALSis replaced byAZURE_CLIENT_IDandAZURE_TENANT_ID. AddedADMIN_EMAIL,TF_BACKEND_AZUREand optionalALERT_EMAILS, matchingdeploy-azure.yml'sworkflow_callsecrets. Also added the push-to-maintrigger and changed theAZURE_LOCATIONdefault towestus2(the workflow'svars.AZURE_LOCATION || 'westus2').https://token.actions.githubusercontent.com, with the trust coming from the manually appliedterraform/environments/*/ci-cd-permissionsbootstrap modules.gh variable set AWS_ROLE_TO_ASSUMEfromterraform output -raw role_arn, plus the trust fromrole.tfandgithub_oidc.tf:sts.amazonaws.com.StringEqualsagainst the literalrole.tflist.dev/staging/prod,aws-fargate-*,aws-db-*) to the jobs that present it. All are inrole.tf. Itsref:refs/heads/mainentry has no current consumer.gh variable setfor the provider and service account from the module outputs, plus the WIF trust fromgithub_oidc.tf:allowed_audiencesset).repository_owner_id,repository_idandref == deploy_ref(defaultrefs/heads/main), with nosubcheck. GCP jobs therefore authenticate only fromdeploy_ref, whatever environment they bind.roles/run.admin: the GCP module does still grant it at project level, as one of 22 roles inlocal.deploy_rolesinservice_account.tf. The README no longer has a hand-run single-role grant. It now says the SA's roles are that list (terraform output roles), and that you change the list and re-apply the module rather than granting by hand, following the CLAUDE.md bootstrap/runtime split. The GCP "Permission denied" troubleshooting snippet got the same treatment.api://AzureADTokenExchange), environment subjects mapped to jobs, and the knownazure-db-*gap (migrate-azurefails withAADSTS70021until those environments are created, gated and added togithub_environments).use_immutable_subject: true.gh api repos/LeanerCloud/cloud-commitments-platform/actions/oidc/customization/subreturns the prefixrepo:LeanerCloud@86753534/cloud-commitments-platform@1391259743.gh apicommand to check it. It explains that the AWS and Azure modules build subjects asrepo:<github_repo>:..., sogithub_repomust be theowner@id/repo@idform.Verification
This change is docs only, so the evidence comes from grep and lint. Nothing was deployed, no Terraform was applied, and no cloud API was called. The only live call was the read-only
gh api .../oidc/customization/subabove.Fail before, pass after. Pattern:
AZURE_CREDENTIALS|sdk-auth|create-for-rbac|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|GCP_SA_KEY|keys create|add-iam-policy-binding|default: eastusThe only remaining hit is the sentence saying
--sdk-authJSON is not created.No workflow reads a static key.
git grep -n -E "AZURE_CREDENTIALS|AWS_ACCESS_KEY_ID|AWS_SECRET_ACCESS_KEY|GCP_SA_KEY|credentials_json|aws-access-key-id" -- '.github/workflows/*.yml'returns nothing (exit 1). The only other repo hits are quotes indocs/audits/codebase-audit-2026-09-02.md, a verbatim audit record that markdownlint ignores. I left it unchanged.Job-to-environment tables: generated by parsing every job in
.github/workflows/*.ymlthat runs one of the three auth actions, with itsenvironment:binding. I checked them against the subject lists inrole.tfandsp.tf.Lint and secret scanning (in a temp dir, using the repo's
.markdownlint.yaml):npx markdownlint-cli@0.47.0 .github/workflows/README.md(the version pinned in.pre-commit-config.yaml) exits 0.npx markdownlint-cli2@0.18.1reports 0 errors.scripts/setup-git-secrets.shand.gitallowedin a freshgit initrepo with an isolated HOME (27 patterns registered).git secrets --scan .github/workflows/README.mdexits 0.scripts/test-git-secrets-allowlist.shreports 137 PASS, 0 FAIL with exit 0, on two fresh isolated-HOME runs. One earlier run, batched with the setup step, exited 1 without saved output, and I could not reproduce it.Other checks: the relative link to the Azure bootstrap README resolves, and the file has no trailing whitespace.
Not run, because no Go, YAML or Terraform changed: Go build/test/vet, golangci-lint, gocyclo, actionlint and terraform.
Out of scope / follow-ups
variables.tfdescriptions underterraform/environments/*/ci-cd-permissions/still showgithub_repoand the subject in the plainLeanerCloud/cloud-commitments-platformform. They don't mention that the immutable prefix is needed, and the Terraform defaults still use the plain name. Rename-proof trust is tracked in sec(iac): rename-proof OIDC deploy trust + branch-restricted environments for the split platform repo #2.main.AWS_ACCOUNT_ID/ECR_REPOSITORY, whichdeploy-aws-lambda.ymldoesn't read.azure-db-*federated credentials themselves, the existing bootstrap-module gap, are documented but not created here.Maintainer: please mirror the issue's triage labels on this PR:
impact/internal,triaged,priority/p3,severity/low,urgency/eventually,effort/xs,type/docs.Closes #28
🤖 Generated with Claude Code