Workload Identity Federation lets a workload outside Google Cloud exchange its own identity token for a short-lived Google Cloud token. GitHub Actions is the classic case: no JSON key in repository secrets.
flowchart LR GH["GitHub Actions job<br/>repo example-org/shop"] -- "OIDC token" --> STS["Security Token Service"] STS --> POOL["Workload identity pool<br/>github-pool + provider"] POOL -- "federated token" --> SA["Service account deployer-sa"] SA --> AR["Artifact Registry and Cloud Run"] %% step GH: The job asks GitHub for an OIDC token that names the repository, branch and workflow. %% step STS: Google’s Security Token Service checks the token against the provider configuration. %% step POOL: The provider maps claims to attributes and the attribute condition rejects other repositories. %% step SA: The federated principal impersonates deployer-sa (or is granted roles directly). %% step AR: The job pushes the image and deploys with tokens that expire within an hour.
Building blocks
| Piece | Purpose |
|---|---|
| Workload identity pool | Container for external identities, per project |
| Provider | Trust settings for one IdP: OIDC (GitHub, GitLab, Azure AD), SAML, or AWS |
| Attribute mapping | Turns token claims into Google attributes, e.g. google.subject=assertion.sub, attribute.repository=assertion.repository |
| Attribute condition | CEL expression that must be true, e.g. assertion.repository_owner=='example-org' |
| IAM binding | Grants roles to principalSet://…/attribute.repository/example-org/shop |
Always set an attribute condition for multi-tenant issuers such as GitHub; otherwise any GitHub repository could try to use your provider.
Setup in four commands
gcloud iam workload-identity-pools create github-pool --location=global
gcloud iam workload-identity-pools providers create-oidc github --location=global --workload-identity-pool=github-pool --issuer-uri="https://token.actions.githubusercontent.com" --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" --attribute-condition="assertion.repository_owner=='example-org'"
gcloud iam service-accounts add-iam-policy-binding deployer-sa@dl-lab-dev.iam.gserviceaccount.com --role=roles/iam.workloadIdentityUser --member="principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/github-pool/attribute.repository/example-org/shop"
In the workflow, google-github-actions/auth with workload_identity_provider and service_account does the exchange; the job also needs permissions: id-token: write.
Workforce vs workload
| Workload Identity Federation | Workforce Identity Federation | |
|---|---|---|
| For | Machines and pipelines | People from an external IdP (Okta, Entra ID) |
| Configured in | A project | The organization |
| Command | Meaning |
|---|---|
gcloud iam workload-identity-pools list --location=global |
Pools in the project |
gcloud iam workload-identity-pools providers describe github --workload-identity-pool=github-pool --location=global |
Provider settings |
Traps: the IAM member uses the project number, not the project ID; forgetting id-token: write gives “unable to get ACTIONS_ID_TOKEN_REQUEST_URL”; without an attribute condition the trust is far too wide.