GCP for DevOps — Workload Identity

3 minute read

Workload Identity

Workload Identity is the recommended way for GKE pods to authenticate to Google Cloud APIs. It maps a Kubernetes service account (KSA) to a Google Cloud service account (GSA), eliminating the need for service account keys.

Why Workload Identity?

         WITHOUT Workload Identity          WITH Workload Identity
         ─────────────────────────          ──────────────────────

         ┌──────────┐                       ┌──────────┐
         │ GKE Pod  │                       │ GKE Pod  │
         └────┬─────┘                       └────┬─────┘
              │                                   │
         Uses node's SA                     KSA mapped to GSA
         or SA key file                     (no keys needed)
              │                                   │
         ⚠️ Every pod on                    ✅ Each pod gets
           the node gets                      only the access
           the same access                    it needs
Without Workload Identity With Workload Identity
All pods share node SA Each pod gets its own identity
SA keys stored as Secrets No keys to manage
Keys can be leaked No keys to leak
Broad permissions Least-privilege per workload
Manual key rotation Automatic token refresh

How It Works

┌────────────────────────────────────────────────────────┐
│                  GKE Cluster                           │
│                                                        │
│  ┌──────────┐    ┌─────────────────────────────────┐  │
│  │  Pod      │    │ Kubernetes Service Account (KSA)│  │
│  │  (app)    │───▶│ name: my-app-ksa               │  │
│  └──────────┘    │ namespace: default               │  │
│                  └──────────────┬──────────────────┘  │
│                                 │                      │
│                    annotation:  │                      │
│                    iam.gke.io/  │                      │
│                    gcp-service- │                      │
│                    account      │                      │
└─────────────────────────────────┼──────────────────────┘
                                  │
                     ┌────────────▼────────────┐
                     │ Google Service Account   │
                     │ (GSA)                    │
                     │ my-app@proj.iam.        │
                     │ gserviceaccount.com      │
                     └────────────┬────────────┘
                                  │
                     Roles: roles/storage.objectViewer
                            roles/cloudsql.client

Setting Up Workload Identity

Step 1: Enable Workload Identity on the Cluster

# Create a new cluster with Workload Identity enabled
$ gcloud container clusters create my-cluster \
    --zone=us-central1-a \
    --workload-pool=my-project.svc.id.goog \
    --num-nodes=3

# Or enable on an existing cluster
$ gcloud container clusters update my-cluster \
    --zone=us-central1-a \
    --workload-pool=my-project.svc.id.goog

Updating my-cluster...done.

Step 2: Create a Google Service Account (GSA)

$ gcloud iam service-accounts create my-app-gsa \
    --display-name="My App GSA"

Created service account [my-app-gsa].

# Grant the necessary roles
$ gcloud projects add-iam-policy-binding my-project \
    --member="serviceAccount:my-app-gsa@my-project.iam.gserviceaccount.com" \
    --role="roles/storage.objectViewer"

Step 3: Create a Kubernetes Service Account (KSA)

$ kubectl create serviceaccount my-app-ksa --namespace=default

serviceaccount/my-app-ksa created

Step 4: Bind KSA to GSA

# Allow the KSA to impersonate the GSA
$ gcloud iam service-accounts add-iam-policy-binding \
    my-app-gsa@my-project.iam.gserviceaccount.com \
    --role="roles/iam.workloadIdentityUser" \
    --member="serviceAccount:my-project.svc.id.goog[default/my-app-ksa]"

Updated IAM policy for serviceAccount [my-app-gsa@my-project.iam.gserviceaccount.com].

Step 5: Annotate the KSA

$ kubectl annotate serviceaccount my-app-ksa \
    --namespace=default \
    iam.gke.io/gcp-service-account=my-app-gsa@my-project.iam.gserviceaccount.com

serviceaccount/my-app-ksa annotated

Step 6: Deploy a Pod Using the KSA

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      serviceAccountName: my-app-ksa    # <-- Use the KSA
      containers:
      - name: my-app
        image: us-docker.pkg.dev/my-project/my-repo/my-app:latest
        resources:
          requests:
            cpu: 100m
            memory: 128Mi

Verifying Workload Identity

# Deploy a test pod
$ kubectl run wi-test \
    --image=google/cloud-sdk:slim \
    --serviceaccount=my-app-ksa \
    --restart=Never \
    --command -- sleep 3600

# Exec into the pod and verify identity
$ kubectl exec -it wi-test -- gcloud auth list

                  Credentialed Accounts
ACTIVE  ACCOUNT
*       my-app-gsa@my-project.iam.gserviceaccount.com

# Test access
$ kubectl exec -it wi-test -- gcloud storage ls gs://my-bucket/

gs://my-bucket/file1.txt
gs://my-bucket/file2.txt

# Clean up test pod
$ kubectl delete pod wi-test

Workload Identity Federation (External Workloads)

Workload Identity Federation extends the concept to non-GCP workloads — GitHub Actions, AWS, Azure, or any OIDC/SAML provider.

┌───────────────────┐     ┌──────────────────┐     ┌──────────────┐
│ GitHub Actions     │     │ Workload Identity │     │ Google Cloud  │
│ (OIDC token)      │────▶│ Pool + Provider   │────▶│ Service Acct  │
└───────────────────┘     └──────────────────┘     └──────────────┘
# Create a Workload Identity Pool
$ gcloud iam workload-identity-pools create github-pool \
    --location=global \
    --display-name="GitHub Actions Pool"

# Create a provider for GitHub
$ gcloud iam workload-identity-pools providers create-oidc github-provider \
    --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=='my-org/my-repo'"

# Grant the pool access to a service account
$ gcloud iam service-accounts add-iam-policy-binding \
    ci-deployer@my-project.iam.gserviceaccount.com \
    --role="roles/iam.workloadIdentityUser" \
    --member="principalSet://iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/github-pool/attribute.repository/my-org/my-repo"

GitHub Actions Example

# .github/workflows/deploy.yml
name: Deploy to GCP
on:
  push:
    branches: [main]

permissions:
  id-token: write    # Required for OIDC
  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/123456789/locations/global/workloadIdentityPools/github-pool/providers/github-provider'
          service_account: 'ci-deployer@my-project.iam.gserviceaccount.com'

      - name: Deploy to Cloud Run
        uses: google-github-actions/deploy-cloudrun@v2
        with:
          service: my-service
          region: us-central1
          image: us-docker.pkg.dev/my-project/my-repo/app:latest

Common Mistakes

Mistake Why It’s Wrong Fix
Not enabling workload pool on cluster Workload Identity won’t work Set --workload-pool on cluster
Forgetting KSA annotation Pod can’t map to GSA Add iam.gke.io/gcp-service-account annotation
Wrong namespace in binding [namespace/ksa] must match exactly Double-check namespace
Using node SA instead All pods get same broad access Always use Workload Identity
Missing roles/iam.workloadIdentityUser KSA can’t impersonate GSA Grant the role on the GSA

Quick Reference

Command Description
--workload-pool=PROJECT.svc.id.goog Enable Workload Identity on cluster
gcloud iam service-accounts add-iam-policy-binding GSA --member="serviceAccount:PROJECT.svc.id.goog[NS/KSA]" --role=roles/iam.workloadIdentityUser Bind KSA to GSA
kubectl annotate sa KSA iam.gke.io/gcp-service-account=GSA Annotate KSA
gcloud iam workload-identity-pools create Create WIF pool
gcloud iam workload-identity-pools providers create-oidc Create WIF provider

Next → Organization Policies

Updated: