Lesson 26 of 28
Module 7 · Task — Scope a ServiceAccount, bind a Role, sketch Workload Identity Federation (via Claude)
The task
Drive Claude to create a dedicated ServiceAccount + namespace-scoped Role + RoleBinding for a pod that needs to list ConfigMaps (and nothing else). Prove — with kubectl auth can-i — that the SA has exactly the permission it needs and none beyond it. Then, on paper, sketch how the same SA would federate to a GCP IAM role via Workload Identity so the pod could read from a GCS bucket without a long-lived key.
Acceptance test: for a pod running as the new SA, kubectl auth can-i list configmaps --as=system:serviceaccount:rbac-demo:reader-sa -n rbac-demo returns yes, AND kubectl auth can-i get secrets --as=... -n rbac-demo returns no, AND kubectl auth can-i list configmaps --as=... -n default returns no (scoped to the right namespace).
Setup
kindcluster from earlier modules running (or re-create it).- A fresh namespace
rbac-demo(kubectl create namespace rbac-demo).
Drive it through Claude
Scaffold the identity primitives. Send Claude:
"Write a single YAML file
rbac.yamlcontaining (a) a ServiceAccountreader-sain namespacerbac-demo, (b) a Roleconfigmap-readerin the same namespace with verbs[get, list, watch]on resourceconfigmaps(not secrets, not pods, not events — just configmaps), (c) a RoleBindingreader-bindingin the same namespace bindingreader-satoconfigmap-reader. Apply it."Open
rbac.yaml. Ask Claude: why is the RoleBinding in the same namespace as the Role and the SA? what would break if the RoleBinding were in a different namespace? Make sure the answer makes sense before applying.Prove the positive case.
"Run
kubectl auth can-i list configmaps --as=system:serviceaccount:rbac-demo:reader-sa -n rbac-demoand tell me the exit status."Expected:
yes.Prove the negative cases (this is the important half).
"For the same SA, check these and tell me the result of each:
can-i get secrets -n rbac-demo,can-i create configmaps -n rbac-demo,can-i list configmaps -n default,can-i list configmaps(no namespace),can-i delete configmaps -n rbac-demo."Expected:
nofor all of them. This is where least-privilege is proven — by showing what the SA can't do, not just what it can.Deploy a pod as the SA.
"Deploy a minimal Pod
reader-podinrbac-demorunningbitnami/kubectl:latest, commandsleep infinity, withserviceAccountName: reader-sa. Once Running,kubectl execinto it and runkubectl get configmapsthenkubectl get secretsand tell me each result."Expected inside the pod:
kubectl get configmapssucceeds (no ConfigMaps yet, but no auth error — so create one withkubectl create configmap test --from-literal=x=y -n rbac-demobeforehand to make the positive case meaningful),kubectl get secretsfails with a forbidden error.Sketch the cloud bridge. No running cloud cluster required — this is a paper exercise to cement the pattern.
"Write
workload-identity-sketch.mddescribing how to extendreader-saso a pod running as it can read from a GCP Cloud Storage bucket calledmy-org-reportswithout any long-lived key. Include: the GCP service account that would be created, theroles/iam.workloadIdentityUserbinding, the KSA annotation, the IAM role on the GCS bucket (roles/storage.objectViewerscoped to the bucket), and one sentence on what a successfulgcloud auth listfrom inside the pod would show. Do not assume you have a GCP project — this is a sketch."Read it. Ask Claude: what's the blast radius of this setup if the reader-pod is compromised? what does an attacker get, and what do they NOT get? The correct answer: read-only access to one specific bucket, for the duration of one pod's lifetime — nothing else. That's what least-priv + short-lived tokens buys you.
Break it on purpose
Observe what an overly-generous binding silently enables.
- Edit
rbac.yaml— change the Role's verbs from[get, list, watch]to["*"](all verbs). Apply. - Predict in writing: what additional
can-ichecks will now returnyesthat used to returnno? - Run
can-i delete configmaps -n rbac-demo --as=...,can-i create configmaps -n rbac-demo --as=...,can-i update configmaps -n rbac-demo --as=.... Confirm your prediction. - A subtle one: run
can-i get secrets -n rbac-demo --as=.... Does this returnyesorno? Why? (Answer:no—resources: configmapsstill limits which resources the verbs apply to;"*"for verbs only widens the action, not the target. Easy to misread which dimension is being opened up.) - Revert the verbs to
[get, list, watch]and reapply.
The failure mode: privilege grants have two dimensions (verbs × resources) and it's easy to widen one while thinking you widened the other. Every real RBAC review is exactly this kind of dimensional analysis.
A note on binding shape
The RoleBinding you wrote binds a Role to an SA — namespace-scoped on both ends. That's the safest shape. For reference, the other three shapes and when you'd use them:
- ClusterRoleBinding → ClusterRole → ServiceAccount: an SA that needs to read across all namespaces (e.g., a monitoring operator). Use sparingly.
- RoleBinding → ClusterRole → ServiceAccount: use the reusable definition from a ClusterRole but scope it to one namespace. This is how you re-use
view,edit,adminClusterRoles for namespace-level grants. - ClusterRoleBinding → ClusterRole → User/Group: human cluster administrators. Not workloads.
If you can't explain in one sentence why you need a ClusterRoleBinding, use a RoleBinding.
Acceptance test
kubectl auth can-i list configmaps --as=system:serviceaccount:rbac-demo:reader-sa -n rbac-demo
# yes
kubectl auth can-i get secrets --as=system:serviceaccount:rbac-demo:reader-sa -n rbac-demo
# no
kubectl auth can-i list configmaps --as=system:serviceaccount:rbac-demo:reader-sa -n default
# no
All three must return the expected answer. If any one is wrong, the scoping is wrong.
What to keep for the next lesson
Keep rbac.yaml, the Claude transcript, your notes from Break it on purpose, and workload-identity-sketch.md. In the next lesson you'll codify .claude/skills/rbac-iam-scaffold/ so that a fresh Claude session, given a service's API needs, can produce a correctly-scoped SA + Role + RoleBinding — and explicitly knows what it cannot do safely (cloud-specific federation, which is why the sketch belongs in your notes rather than baked into the skill).