GCP for DevOps — Workload Identity
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