ci: authenticate to Google Cloud keylessly with WIF - #230
Merged
Conversation
GCP_SA_KEY, the long-lived service-account key, leaked and is disabled, so build.yaml fails at auth today. Replace it with Workload Identity Federation: each run presents its GitHub OIDC token and receives a 15-minute access token that is never written to the workspace. Order matters: npm ci --ignore-scripts and npm run build now finish before the auth step, so dependency install scripts never execute while a cloud credential is live, and a guard asserts the workspace holds no credential file before npm publish and before the docker build context is sent. Also adds a permissions: block to every workflow and pins every action to a SHA. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMNuJzL2Lctj1jeabkLury
Collaborator
Author
|
Keyless path verified end to end before merge. After gitops-infrastructure #1811 was applied (pool + provider + Evidence from that run:
Account state: zero user-managed keys, zero project-level roles, Ready to merge. |
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
GCP_SA_KEY— the long-lived Google service-account key this workflow used — leaked and has been disabled, soDocker Build CI/CDcurrently fails at the auth step. This replaces it with Workload Identity Federation and rewrites the job so a live credential can never reach dependency code again.google-github-actions/authnow takesworkload_identity_provider+service_accountinstead ofcredentials_json, withtoken_format: access_token,create_credentials_file: falseandaccess_token_lifetime: 900s. There is noGCP_SA_KEYanywhere in.github/any more, and no credential file is written into$GITHUB_WORKSPACE.env.GCP_SA_KEY(which exported the raw key into every step, includingnpm ci's install scripts) is gone. The minted token is referenced only assteps.auth.outputs.access_token, by the GAR login, the ghcr login andnpm publish.npm ci --ignore-scriptsandnpm run buildnow run before authentication. After the auth step only the registry logins,npm publish --ignore-scriptsand the docker build remain. AnORDERING RULEcomment at the top of the job records why.gcloud auth configure-dockeris replaced bydocker/login-action(writes~/.docker/config.json, logs out in its post step); the hand-rolledecho … > .npmrc/rm -f .npmrcpair is replaced byactions/setup-nodewithregistry-url+scope: '@o1-labs', which keeps its.npmrcin$RUNNER_TEMP, plus a per-stepNODE_AUTH_TOKENon the publish step only.npm publishand again right before the docker build context is sent — failing the job if anygha-creds-*.json,.npmrcor dotenv file is in the workspace root, or ifnpm packwould ship one.gha-creds-*.jsonadded to.gitignore,.dockerignoreand.npmignore;.npmrcadded to.gitignore.permissions:block, and every non-localuses:is pinned to a 40-char commit SHA with the tag kept as a trailing comment so Dependabot'sgithub-actionsgroup still works.Tag formats, image names, registries, the npm version scheme, the fork/Dependabot
if:guard and the buildx layer cache are all unchanged.Docs: new "CI credentials" subsection in
docs/versioning.md, one constraint line inAGENTS.md.⛔ Do not merge until the Terraform is applied
This PR references a WIF pool, provider and service account that do not exist yet:
projects/1020762690228/locations/global/workloadIdentityPools/github-actions/providers/githubarchive-node-api-ci@o1labs-192920.iam.gserviceaccount.comThey come from the
o1-labs/gitops-infrastructurePR that addsplatform/gcloud/service-accounts/workload-identity.tf(WIF pool + provider) and thearchive-node-api-cientry inservice-accounts.tfvars(SA + the per-repositoryroles/iam.workloadIdentityUserbinding forattribute.repository/o1-labs/Archive-Node-API). Merging before a human applies that Terraform makes every build fail at auth. Keep this PR in draft until then.Follow-up human steps after the first green WIF run: delete the
GCP_SA_KEYrepository secret, and apply the repository-settings half of issue 06 (default_workflow_permissions=read,allowed_actions=selected,sha_pinning_required=true, branch protection) — deliberately not done here.Deviation from the spec worth reviewing
Issue 04's guard script uses
-name '.env.*', which matches the committed, secret-free.env.example.composeand.env.example.lightnet— verified locally, the script as written fails on a clean checkout every time. The predicate is\( -name '.env.*' ! -name '.env.example.*' \)here, with a comment saying why. Everything else in the script is verbatim.Acceptance criteria
Issue 04 — no credential on disk
grep -c 'gha-creds-\*.json' .gitignore .dockerignore .npmignoreprints1for each;grep -c '^\.npmrc$' .gitignore .npmignoreprints1for each.create_credentials_file: falseandtoken_format: access_token;grep -nE 'configure-docker|print-access-token|> \.npmrc|rm -f \.npmrc' .github/workflows/build.yamlreturns nothing.Created credentials fileand noGOOGLE_APPLICATION_CREDENTIALS:line — needs a CI run.package.json,README.md,schema.graphql,tsconfig.jsonand paths underbuild/,src/) — needs a CI run; verified locally: 216 files, nothing outside that set.@o1-labs/mina-archive-node-graphql@<version>-dev.<sha>and pushesdev-<sha>— needs a CI run.transferring context:stays around 1 MB — needs a CI run.Issue 03 — scope the credential, run npm first
GCP_SA_KEYunder any workflow- or job-levelenv:— there is no reference to it anywhere in.github/at all.yq '.jobs["build-and-deploy"].steps[].name'listsInstall dependenciesandBuildbeforeAuthenticate to Google Cloud; the only npm step after it isnpm publish.npm ci --ignore-scripts;--ignore-scriptson bothnpm publishbranches.printenv | grep -c '^GCP_SA_KEY='probe prints0afterInstall dependenciesand after the auth step — needs a throw-away-branch run (structurally guaranteed: the secret no longer exists and nothing is exported).npm run test:unitpasses locally afternpm ci --ignore-scripts— 21 suites,fail 0in every one.-dev.<sha>package and pushes thedev-<sha>image — needs a CI run.Issue 02 — Workload Identity Federation (workflow part)
grep -rnE 'GCP_SA_KEY|credentials_json' .github/prints nothing.yq '.permissions' .github/workflows/build.yamlprints exactlycontents: read,id-token: write,packages: write.gh secret list --repo o1-labs/Archive-Node-APIprints nothing — human step after the first green WIF run.gcloud iam service-accounts keys list --iam-account=archive-node-api-ci@… --managed-by=userprints nothing — after Terraform apply.v*tag build succeed on WIF; the auth step printsSuccessfully authenticatedwithoutCreated credentials file— after Terraform apply.serviceAccountKeyNameand aprincipalSubjectbeginningprincipal://iam.googleapis.com/projects/1020762690228/…/subject/repo:o1-labs/Archive-Node-API:— after Terraform apply.Issue 06 — hardening (workflow part only)
grep -L '^permissions:' .github/workflows/*.y*mlprints nothing.grep -hoE 'uses: [^@ ]+@[^ ]+' .github/workflows/*.y*ml | grep -vE '@[0-9a-f]{40}$'prints nothing.# vX.Y.Zcomment;anchore/sbom-actionleft as it was; no version bumped.sha_pinning_required: true— needs the repository setting, which is out of scope for this PR.mainafter merge — needs a CI run.github-actionsgroup PR updates SHAs and comments together — wait for the weekly run.Repository-settings criteria from issue 06 (
actions/permissions/workflow,selected-actions, branch protection) are deliberately not addressed here; they aregh apicalls needing admin approval.Negative tests to run once on a throw-away branch, then delete
- run: echo '{}' > gha-creds-test.jsonimmediately before the first guard. The run must fail at the guard with::error::credential file(s) present in workspace:. (Reproduced locally against the exact script body: exits 1, prints./gha-creds-test.json.)- run: printenv | grep -c '^GCP_SA_KEY=' || trueright afterInstall dependenciesand again right after the auth step; both must print0.sha_pinning_required: trueis set on the repository, referenceactions/checkout@v7on a branch; the run must refuse to start with GitHub's "must be pinned to a full-length commit SHA" error.gitops-images), use the same provider withservice_account: archive-node-api-ci@…; auth must fail withPERMISSION_DENIED/unable to acquire impersonated credentials.Link each failed run here, then delete the branches.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BMNuJzL2Lctj1jeabkLury