Skip to main content

Overview

This runbook captures the steps performed to set up Workload Identity Federation (WIF) on GCP project solution-comunity and to bring the existing WIF resources under Terraform management via terraform import. It is intended as a handover artifact for the team: it explains the underlying concepts, walks through the Terraform import configuration that was used, and explains why a separate state prefix was chosen for the import.

The reader is assumed to be comfortable with Terraform basics, GCP IAM, and GitHub Actions.

What is Workload Identity Federation (WIF)?

Workload Identity Federation is a GCP IAM feature that lets workloads running outside Google Cloud — for example, GitHub Actions runners, GitLab CI jobs, AWS EC2 instances, or on-prem Kubernetes clusters — call Google Cloud APIs without using a long-lived service account key. Instead of issuing and storing a JSON key, the external workload presents an OIDC or SAML token issued by its own identity provider (IdP). GCP validates that token against a configured trust relationship and, if it matches, exchanges it for a short-lived federated access token that can be used to impersonate a Google service account.

The result is the same set of permissions you would have had with a service account key, but with two important differences: there is no static credential to leak, and access can be tightly scoped using attributes from the original token (repository name, branch, environment, etc.). For GitHub Actions specifically, this means a workflow can authenticate to GCP using nothing more than the OIDC token GitHub mints for that workflow run.

Conceptually, WIF is built from three pieces: a workload identity pool (the trust boundary), one or more providers under that pool (the configured IdPs and how their tokens map to attributes), and an IAM binding that lets a federated principal impersonate a Google service account. We set up all three for this project.

Service Accounts vs. Workload Identity Federation

A Google service account is the identity a workload uses inside GCP. Traditionally, an external CI system would authenticate as a service account by presenting a downloaded JSON key. The key is a long-lived secret: anyone who obtains it can act as that service account until the key is rotated or disabled. Keys also tend to sprawl across CI secret stores, developer laptops, and ticket attachments, which makes them hard to audit.

WIF does not replace the service account — the workload still acts as one — but it replaces the credential. The external workload presents an OIDC token from its native IdP (GitHub in our case), GCP validates the token against a workload identity pool provider, and STS issues a short-lived access token that impersonates the target service account. There is no JSON key to store or rotate, the token has a short TTL, and access can be conditioned on token attributes such as the repository or branch.

Summary: service accounts answer "who is acting?"; WIF answers "how does an outside workload prove it is allowed to act as that service account, without a long-lived key?"

AspectService Account KeyWIF + Service Account
Credential typeLong-lived JSON keyShort-lived OIDC token exchanged for STS token
StorageStored in CI secret managerNothing to store — minted at runtime
RotationManual, easy to forgetAutomatic — every workflow run
Blast radius if leakedFull SA access until revokedToken expires within minutes
ScopingPer service account onlyPer repo, branch, environment, etc.
Audit clarityHard to tie back to a workflowToken claims identify the originating workload

What is a Workload Identity Pool?

A workload identity pool is a logical container in GCP IAM that groups external identities and defines a trust boundary. Every federated identity has to belong to exactly one pool, and IAM bindings reference principals through the pool, e.g. principalSet://iam.googleapis.com/projects/<num>/locations/global/workloadIdentityPools/<pool>/...

A pool is essentially a namespace and a policy boundary. You typically create one pool per environment, per use case, or per external system you trust — for example, a github-actions-pool for all GitHub-issued tokens, and a separate aws-prod-pool for an AWS account. The pool itself does not know how to validate tokens; that responsibility belongs to the providers you attach to it. Disabling or deleting the pool revokes federation for every provider and identity beneath it, which makes the pool a convenient kill-switch.

For this project we created the pool github-actions-pool under project solution-comunity, at the global location.

What is a Workload Identity Pool Provider?

A provider is the component attached to a pool that actually validates incoming tokens. It tells GCP three things: which IdP it trusts (issuer URI plus, for OIDC, the JWKS that signs the tokens), how to translate token claims into Google IAM attributes (the attribute mapping), and which tokens are allowed in at all (the optional attribute condition).

The attribute mapping converts claims from the external token into the google.subject, attribute.repository, attribute.ref, etc. that IAM bindings can refer to. The attribute condition is a CEL expression that rejects tokens that do not match — for example, only allowing tokens whose repository claim equals our org/repo. This is where you enforce that random GitHub workflows from unrelated organisations cannot exchange tokens against your pool, even though they share the same issuer.

For this project we created the provider github-actions-provider under github-actions-pool, pointing at GitHub’s OIDC issuer.

How WIF Connects to GitHub

GitHub Actions runners can request an OIDC token from GitHub’s token service for any workflow that declares the id-token: write permission. The token is a JWT signed by GitHub that includes the repository, the ref, the workflow name, the environment, and the actor. WIF connects to GitHub by trusting that issuer and that JWKS, then mapping those claims into IAM attributes.

The end-to-end flow is:

  • The workflow asks GitHub for an OIDC token.
  • The workflow calls the google-github-actions/auth action with the WIF provider resource name and the target service account.
  • The action sends the GitHub token to Google STS, which validates the signature, the audience, and the attribute condition against our provider.
  • STS returns a short-lived federated credential that impersonates the service account.
  • Subsequent gcloud / Terraform / API calls use that token. No JSON key ever leaves Google.

A typical workflow snippet looks like this:

permissions:
id-token: write # required to mint the GitHub OIDC token
contents: read

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider
service_account: github-action-1078621049@solution-comunity.iam.gserviceaccount.com
- run: gcloud projects describe solution-comunity

On the GCP side, the service account must grant the federated principal the roles/iam.workloadIdentityUser role, scoped to the specific repository (via principalSet://...attribute.repository/Solution-Community/terraform). That binding is what allows the GitHub workflow to impersonate github-action-1078621049@solution-comunity.iam.gserviceaccount.com. The binding itself is created by the reusable iam_binding module.