ServicesAboutNotesContact Get in touch →
EN FR
Note

Workload Identity Federation for CI/CD

Replace service account keys in GitHub Actions and other CI systems with keyless OIDC authentication — no credentials to store, rotate, or leak.

Planted
gcpdata engineeringautomation

Service account keys stored as CI/CD secrets are a common source of credential leaks in GCP environments. They persist long after the pipeline that needed them has changed, get copied to local machines for testing, and when they leak — through a repository accident, a compromised CI runner, or an inadvertent log — they work from anywhere until rotated.

Workload Identity Federation (WIF) eliminates the key. Instead of storing a credential, CI systems authenticate using their native identity mechanism (OIDC tokens) and exchange that for short-lived GCP credentials on demand.

How It Works

The flow:

  1. GitHub Actions (or GitLab CI, etc.) generates a short-lived OIDC token proving “I am workflow X in repository Y, triggered by event Z”
  2. That token is exchanged with GCP’s Workload Identity Pool for a short-lived GCP access token
  3. The access token is scoped to the service account you’ve configured
  4. The token expires after one hour — it can’t be reused outside that window

No key file. No secret to rotate. The credential is minted fresh for each pipeline run and expires automatically.

Setting Up for GitHub Actions

The GCP side requires a Workload Identity Pool and Provider:

Terminal window
# Create the identity pool
gcloud iam workload-identity-pools create github \
--project=YOUR_PROJECT_ID \
--location=global \
--display-name="GitHub Actions Pool"
# Add GitHub as an OIDC provider
gcloud iam workload-identity-pools providers create-oidc github \
--project=YOUR_PROJECT_ID \
--location=global \
--workload-identity-pool=github \
--display-name="GitHub Actions Provider" \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
--issuer-uri="https://token.actions.githubusercontent.com"

The attribute-mapping translates GitHub’s OIDC token claims to GCP attributes. assertion.repository becomes an attribute you can use in conditions — critical for ensuring only your repositories can use this pool.

Grant the service account access from the pool, restricted to specific repositories:

Terminal window
gcloud iam service-accounts add-iam-policy-binding \
wlif-github-actions@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--project=YOUR_PROJECT_ID \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github/attribute.repository/your-org/your-repo"

The principalSet binding scopes access to a specific repository. A compromised token from a different repository in the same GitHub organization cannot impersonate this service account.

The GitHub Actions Workflow

Once the GCP side is configured, the workflow is three lines:

permissions:
id-token: write # Required for OIDC token generation
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: google-github-actions/auth@v2
with:
workload_identity_provider: 'projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/github/providers/github'
service_account: 'wlif-github-actions@YOUR_PROJECT_ID.iam.gserviceaccount.com'
- name: Run dbt
run: dbt run --target prod

The google-github-actions/auth action handles the OIDC token exchange. After it runs, all subsequent steps in the job have GCP credentials available via Application Default Credentials — the same mechanism that any GCP SDK uses.

Naming the Service Account

Following the per-workload naming convention, the wlif- prefix marks this as a Workload Identity Federation service account. This makes it immediately identifiable in audit logs and IAM policy listings. When you see wlif-github-actions in a BigQuery JOBS query, you know it’s a CI pipeline running against production.

Create separate service accounts for separate pipelines:

  • wlif-github-actions@project.iam.gserviceaccount.com — for application deployments
  • wlif-dbt-ci@project.iam.gserviceaccount.com — for dbt CI runs
  • wlif-terraform-ci@project.iam.gserviceaccount.com — for infrastructure changes

Each gets only the permissions its pipeline needs. The dbt CI service account needs bigquery.dataViewer to run tests; it doesn’t need bigquery.dataEditor. The Terraform CI account needs IAM admin permissions; the dbt account definitely doesn’t.

GitLab CI and Other Platforms

GitHub Actions is the most common case, but WIF supports any OIDC-compatible identity provider. GitLab, CircleCI, and most other CI platforms support OIDC authentication against external providers.

The GCP configuration is nearly identical — create an OIDC provider pointing at the platform’s token endpoint, configure attribute mapping for the relevant claims (repository, project, branch), and bind service account access to specific attribute values.

The workflow-side configuration varies by platform. Check the platform’s documentation for how to generate OIDC tokens and pass them to external services. The key invariant: you’re always exchanging a platform-native token for short-lived GCP credentials, never storing a key.

What to Do with Existing Keys

Once WIF is configured and working, the existing service account keys can be deleted. Not rotated — deleted. They’re no longer needed.

Terminal window
# List keys for the service account
gcloud iam service-accounts keys list \
--iam-account=wlif-github-actions@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--filter="keyType=USER_MANAGED"
# Delete each key (replace KEY_ID with the actual key ID)
gcloud iam service-accounts keys delete KEY_ID \
--iam-account=wlif-github-actions@YOUR_PROJECT_ID.iam.gserviceaccount.com

Run the service account key audit after migrating all pipelines to confirm no keys remain. The goal state is zero user-managed keys across all workload service accounts.