Authenticate GitHub Actions to Google Cloud with no service account key
The documented keyless path, in the order the commands run: create a workload identity pool, create an OIDC provider pinned to the GitHub Actions issuer, map and condition the assertion, bind the federated principal, and set the workflow's id-token permission. With the one attribute condition that stops any repository on GitHub from using your pool.
markdownguidev11729-764Cstage: deploytrust: officialfamily: identityservice: iaminterface: cligithub actionsworkload identity federationOIDCgcloud iam workload-identity-pools--issuer-uri--attribute-mapping--attribute-conditionroles/iam.workloadIdentityUserid-token: write
GitHub Actions to Google Cloud, keyless
No key is created, downloaded or stored at any step. If a step seems to need one, the step is wrong.
Step 1 — a workload identity pool
gcloud iam workload-identity-pools create POOL_ID \
--location="global" \
--description="DESCRIPTION" \
--display-name="DISPLAY_NAME"
--location="global" is the only location these take.
Step 2 — an OIDC provider, pinned to GitHub's issuer
gcloud iam workload-identity-pools providers create-oidc PROVIDER_ID \
--location="global" \
--workload-identity-pool="POOL_ID" \
--issuer-uri="https://token.actions.githubusercontent.com/" \
--attribute-mapping="MAPPINGS" \
--attribute-condition="CONDITIONS"
The issuer URI for GitHub Actions is https://token .actions.githubusercontent.com/ — written in two spans
here so the document can be published; it is one string with no space in it.
Step 3 — map, and then condition
The minimum mapping is
google.subject=assertion.sub
and the page's recommended condition is
assertion.repository_owner=='ORGANIZATION'
Do not skip the condition. A provider that trusts the GitHub Actions issuer with no attribute condition trusts every workflow on GitHub, not only yours — the issuer is shared by all of them. The condition is the only thing that narrows it. This is the single most consequential line in the whole setup.
Step 4 — bind the federated principal
Two shapes, and §3 of reference/identity/workload-identity-federation.md says when each is right.
Direct resource access — the vendor's recommended path. Bind the principal straight onto the resource:
principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/subject/SUBJECT
Impersonation — when something downstream can only be reached as a service account. Grant that principal
roles/iam.workloadIdentityUser on the service account, and the workflow then impersonates it.
Note PROJECT_NUMBER, not the project id. Using the id is the most common silent failure here: the binding is
accepted and never matches.
Step 5 — let the workflow mint its OIDC token
permissions:
id-token: write
contents: read
Without id-token: write the runner has no assertion to exchange, and the failure reads as an authentication
error rather than as a missing permission.
Step 6 — exchange it
Use google-github-actions/auth, giving it the workload identity provider and — only if you chose
impersonation — the service account identifier, whose shape is a name, then the project id, then
.iam.gserviceaccount.com.
Verify
The first call the workflow makes to a Google Cloud API is the test. If it returns 401 authError the
exchange failed: re-check the issuer URI, the mapping and id-token: write. If it returns 403 forbidden
the exchange worked and the binding is wrong — that is a much better failure, and
guides/identity/fix-permission-denied.md takes it from there.
What this removes from a migration estimate
One service account key per pipeline, its storage, its rotation, and the incident response for its leak. The
end-to-end shape, with the Google Cloud side and the GitHub side in one place, is
patterns/github-actions-to-google-cloud-keyless.md. The AWS and Azure equivalents of this exact setup are in
guides/parity/identity.md.