---
cuppy_hub: gcp
cuppy_path: guides/identity/github-actions-without-keys.md
cuppy_section: docs
cuppy_doc: 1729-764C
cuppy_doc_uuid: 91dcf5c0-90dd-4ef0-b050-56e9d5342333
cuppy_permalink: https://cuppy.io/gcp/d/1729-764C
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/gcp/docs/guides/identity/github-actions-without-keys
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: 837dc6acf109e79ff6dc85f6ca9909c2e7093fd528022825f74e03849c2abad4
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# 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

```yaml
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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 91 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/gcp/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
