← Back to posts

GCP ACE: RBAC and Workload Identity Federation in GKE

Two separate access questions come up constantly on the exam, and they’re easy to confuse: who can call the Kubernetes API and do what (RBAC), versus how a Pod’s own code authenticates to Google Cloud APIs like Cloud Storage or BigQuery (Workload Identity Federation). This article covers both.

RBAC — who can do what inside the cluster

Four objects, two pairs:

ObjectScope
RolePermissions within one namespace
ClusterRolePermissions cluster-wide — covers cluster-scoped resources (like nodes), non-resource endpoints (/healthz), or namespaced resources across every namespace
RoleBindingBinds a Role (or a ClusterRole) to subjects, scoped to one namespace — binding a ClusterRole this way limits its effect to just that namespace
ClusterRoleBindingBinds a ClusterRole to subjects across all namespaces

Subjects can be Google Cloud users, Kubernetes ServiceAccounts, IAM service accounts, or Google Groups (the last one only after you configure Google Groups for RBAC, with the groups nested under a gke-security-groups group). RBAC is purely additive — there are no deny rules, so every binding only grants, never restricts.

Example: read-only Pod access in one namespace

# role-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
- apiGroups: [""]              # "" = core API group
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
# rolebinding-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader-binding
  namespace: dev
subjects:
- kind: User
  name: [email protected]
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Equivalent imperative form:

kubectl create role pod-reader --namespace=dev --resource=pods --verb=get,list,watch
kubectl create rolebinding pod-reader-binding --namespace=dev --role=pod-reader --user=[email protected]

Verifying access:

kubectl auth can-i list pods --namespace=dev --as=[email protected]    # yes
kubectl auth can-i delete pods --namespace=dev --as=[email protected]  # no

One rule worth remembering: you can only grant permissions you already hold yourself. To create or update a Role, you need every permission it contains, or the escalate verb on roles. To create or update a binding, you need the permissions in the role being bound (at the same scope), or the bind verb on that role. You can’t create a Role that lets someone do more than you can do. On GKE, if those permissions came to you through IAM rather than RBAC, the request can still fail with attempt to grant extra privileges.

Workload Identity Federation — how Pods authenticate to Google Cloud

This solves a different problem: your app’s code inside a Pod needs to call a Google Cloud API (read from Cloud Storage, write to BigQuery). The insecure default is letting it inherit the node’s service account — meaning every Pod on that node, regardless of what it actually needs, has whatever permissions the node has. Workload Identity Federation scopes that down to the individual workload.

How it works: each workload is identified by its own Kubernetes ServiceAccount (KSA). Google’s recommended approach is to grant IAM roles to that KSA directly, using an IAM principal identifier; the workload then gets a federated access token. The alternative is to link the KSA to a Google Cloud IAM service account (GSA), and GKE exchanges tokens behind the scenes via the IAM Service Account Credentials API. You need the alternative when the API you’re calling doesn’t support federated identities, or its limitations block your use case. Authentication flows through the same metadata-server mechanism as Compute Engine, so client libraries that already read the metadata server typically need zero code changes.

Enable it, once per Standard cluster (Autopilot clusters always have it enabled, on every node):

gcloud container clusters update CLUSTER_NAME \
    --location=CONTROL_PLANE_LOCATION \
    --workload-pool=PROJECT_ID.svc.id.goog

Node pools need the GKE metadata server enabled too (and Pods must be scheduled onto those pools):

gcloud container node-pools create NODEPOOL_NAME \
    --cluster=CLUSTER_NAME \
    --location=CONTROL_PLANE_LOCATION \
    --workload-metadata=GKE_METADATA

Once a node pool has this enabled, Pods on it can no longer fall back to the node’s own service account — a side effect worth knowing, since it’s what actually closes the original security gap.

Recommended: grant a role directly to the KSA. For example, read access to one bucket:

gcloud storage buckets add-iam-policy-binding gs://BUCKET \
    --role=roles/storage.objectViewer \
    --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/NAMESPACE/sa/KSA_NAME \
    --condition=None

Note the principal uses the project number in the path and the project ID in the pool name.

Alternative: link a KSA to a GSA, step by step:

# 1. Create the namespace and KSA
kubectl create namespace NAMESPACE
kubectl create serviceaccount KSA_NAME --namespace=NAMESPACE

# 2. Create the IAM service account
gcloud iam service-accounts create IAM_SA_NAME --project=IAM_SA_PROJECT_ID

# 3. Grant the GSA the roles the workload actually needs
gcloud projects add-iam-policy-binding IAM_SA_PROJECT_ID \
    --member "serviceAccount:IAM_SA_NAME@IAM_SA_PROJECT_ID.iam.gserviceaccount.com" \
    --role "ROLE_NAME"

# 4. Allow the KSA to impersonate the GSA
gcloud iam service-accounts add-iam-policy-binding IAM_SA_NAME@IAM_SA_PROJECT_ID.iam.gserviceaccount.com \
    --role roles/iam.workloadIdentityUser \
    --member "serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME]"

# 5. Annotate the KSA to link it to the GSA
kubectl annotate serviceaccount KSA_NAME \
    --namespace NAMESPACE \
    iam.gke.io/gcp-service-account=IAM_SA_NAME@IAM_SA_PROJECT_ID.iam.gserviceaccount.com

With this linking approach, both step 4 (the IAM allow policy) and step 5 (the annotation) are required — either alone isn’t enough.

Exam Takeaways

Sources

↗ LinkedIn