Right, let’s talk about Workload Identity. This is, without a doubt, the single most important security feature you’ll configure on GKE. It solves a problem that used to be a total nightmare: how do you give your Pods access to other Google Cloud services—like a Cloud Storage bucket or a BigQuery dataset—without being a complete maniac?

The old way was to either: a) download a JSON service account key, bake it into a Kubernetes Secret, and pray to the ops gods it never leaked (it always did), or b) give the node pool’s service account absurdly broad permissions, effectively turning every Pod on your node into a privileged user. Both options are terrible. The first is a key management disaster, and the second is like giving every person in a building the master key to the city. Google rightfully decided this was clown shoes and built a better way.

Workload Identity is that better way. In essence, it lets you do a neat little bit of identity trickery. You tell GKE, “Hey, see this Kubernetes Service Account (my-app-sa) in this namespace (production)? I want you to impersonate this specific Google Cloud IAM Service Account (my-gcp-sa@my-project.iam.gserviceaccount.com) whenever a Pod uses it.” It’s like giving your Pod a temporary, automatically rotating credential that only works for that one specific GCP service account. Elegant, secure, and you never have to handle a JSON key again. The magic behind the curtain is that GKE hooks into the node’s metadata server to provide tokens for the mapped GSA, not the node’s own identity.

Enabling and Configuring Workload Identity

First, you need to make sure it’s turned on. If you’re creating a new cluster, it’s on by default and you’d have to go out of your way to disable it (don’t do that). For an existing cluster, you’ll need to enable it on the node pool. This requires a node pool recreation, so plan for a rolling update.

# Create a new cluster with Workload Identity on (it's the default, but be explicit)
gcloud container clusters create my-cluster \
    --region us-central1 \
    --workload-pool=my-project.svc.id.goog

# For an existing node pool, you have to recreate it. This will cause downtime!
gcloud container node-pools update my-pool \
    --cluster=my-cluster \
    --workload-metadata=GKE_METADATA

The --workload-pool flag is crucial. It defines the pool of identities your cluster will use. The format [PROJECT_ID].svc.id.goog is standard.

The IAM Binding: The Trust Handshake

Here’s where the actual mapping happens. You’re creating a policy binding in GCP IAM that says, “I allow the Kubernetes Service Account my-app-sa in namespace default to impersonate the GCP Service Account my-gcp-sa.”

You do this with gcloud iam service-accounts add-iam-policy-binding. The command is a mouthful, which is how you know it’s powerful.

# This is the magic command that links the two identities
gcloud iam service-accounts add-iam-policy-binding my-gcp-sa@my-project.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:my-project.svc.id.goog[default/my-app-sa]"

Breakdown: You’re binding the roles/iam.workloadIdentityUser role to the GCP SA. The --member is the special part: it’s the full path to your Kubernetes SA within the workload identity pool. Get this string wrong and nothing will work, and the error messages are not always helpful.

Annotating Your Kubernetes Service Account

The last piece of the puzzle is on the Kubernetes side. Your Pod’s service account needs to know which GCP service account it’s supposed to impersonate. You tell it this via an annotation.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  namespace: default
  annotations:
    iam.gke.io/gcp-service-account: my-gcp-sa@my-project.iam.gserviceaccount.com

Now, any Pod that uses serviceAccountName: my-app-sa will automatically have its identity mapped. The Pod doesn’t need any special code or libraries; it can just use the standard Google Cloud client libraries, which automatically look for credentials on the metadata server.

The Code: It Just Works (Seriously)

Here’s the beautiful part. Inside your Pod, any application using the Google Cloud client libraries will automatically find the correct credentials. You don’t need to point it at a file or set an environment variable. It just works.

# Python example using the Cloud Storage client
from google.cloud import storage
from google.auth import default

# This call automatically retrieves the workload identity token
credentials, project = default()
client = storage.Client(credentials=credentials, project=project)

# Now list your buckets like a normal, secure human
buckets = client.list_buckets()
for bucket in buckets:
    print(bucket.name)

Common Pitfalls and How to Avoid Them

  1. The Annotation is Wrong: This is the number one issue. The annotation on your KSA must exactly match the email address of the GSA you’re binding to. A typo here means silence failure.
  2. The Binding is Wrong: The member in the IAM binding must exactly match your Kubernetes service account’s full path: serviceAccount:[PROJECT_ID].svc.id.goog[NAMESPACE/KSA_NAME]. Forgetting the namespace is a classic mistake.
  3. The GSA Has No Permissions: Workload Identity handles authentication (“who are you?”). You still need to give the GCP Service Account the correct IAM permissions for authorization (“what are you allowed to do?”). If your app can’t access a bucket, check the IAM permissions on the bucket for your GSA, not your user account.
  4. Default Service Account Confusion: Kubernetes creates a default service account in every namespace. GCP creates a default service account for your project. These are not the same thing, and you should map your own KSAs to purpose-built GSAs, not the GCP default one. The less privilege, the better.

Once you get this configured, you’ll wonder how you ever lived without it. It turns a security quagmire into a few lines of config. It’s one of those features that just makes sense.