๐Ÿ“– Read-only preview โ€” lessons, diagrams, and quizzes only. The hands-on labs, live grading, and progress tracking require running the real course locally against a Kubernetes cluster.Run it locally โ†’

Module 11 ยท Lesson 11.1

RBAC & ServiceAccounts

Every pod runs as someone. Not a human someone โ€” a ServiceAccount, the identity a pod presents whenever it talks to the Kubernetes API itself. If you've never set one, your pods use the default ServiceAccount of their namespace, which (sensibly) can do almost nothing.

The permission chain

RBAC (Role-Based Access Control) decides what a given identity can do, through three objects chained together:

  • Role (or ClusterRole) โ€” a list of permissions: which verbs (get, list, watch, create, delete, ...) are allowed on which resources (pods, deployments, secrets, ...), optionally scoped to specific resource names. A Role is namespaced; a ClusterRole applies cluster-wide or can be reused across namespaces.
  • RoleBinding (or ClusterRoleBinding) โ€” grants a Role to a subject: a User, a Group, or โ€” the one that matters here โ€” a ServiceAccount.
  • ServiceAccount โ€” the identity itself, attached to a pod via spec.serviceAccountName. Kubernetes mounts its token into the pod automatically, which is how the pod authenticates.

The RBAC permission chain

Podspec.serviceAccountName: report-reader
โ†’
ServiceAccountreport-reader
โ†’
RoleBinding(missing)
โ†’
Rolepod-reader: get,list pods
kubectl auth can-i list pods --as=system:serviceaccount:ns:report-reader โ†’ no
No RoleBinding yet โ€” the Role exists but grants nothing to anyone. Toggle it on.

Nothing is granted by default beyond this chain. No Role, no RoleBinding, no permission โ€” full stop. This is "default deny": the safest possible starting position, but it means a pod that needs to talk to the API (an operator, a controller, a CI job calling kubectl from inside the cluster) needs this chain deliberately wired up, every time.

Reading a Forbidden error

When the chain is missing or incomplete, the API server's rejection is unusually specific โ€” it names the exact subject, verb, resource, and scope it refused:

Error from server (Forbidden): pods is forbidden: User
"system:serviceaccount:my-ns:report-reader" cannot list resource "pods"
in API group "" in the namespace "my-ns"

That single line is almost always enough to write the fix: a Role granting list on pods, bound to report-reader via a RoleBinding, in my-ns. The fastest way to test a fix before touching a real pod is kubectl auth can-i <verb> <resource> --as=system:serviceaccount:<ns>:<name> -n <ns> โ€” it asks the API server to evaluate the same decision without needing a running pod at all.

The infrastructure analogy

This is IAM, reshaped for an API instead of a cloud console: a Role is a policy document, a RoleBinding is a policy attachment, a ServiceAccount is closest to an instance role โ€” an identity your compute assumes, not a human's credentials. If you've attached an IAM role to an EC2 instance or an IRSA role to an EKS pod (more on that in Module 15), this chain will feel immediately familiar โ€” right down to "default deny" being the only sane starting position.

In the lab, you'll hit exactly the Forbidden error above, from inside a real pod using its own mounted identity, and fix it the same way you would in production.

๐Ÿงช Lab: lab-22-rbac-and-serviceaccounts

Preview only

Goal

Hit a real RBAC Forbidden error from inside a pod using its own in-cluster identity, then fix it with the minimal Role + RoleBinding.

Tasks

  1. Apply the starting manifests: kubectl apply -f manifests/
  2. Confirm the reporter pod is Running: kubectl get pods -n lab-22-rbac-and-serviceaccounts
  3. Try to use its identity from inside the pod: kubectl exec -n lab-22-rbac-and-serviceaccounts reporter -- kubectl get pods -n lab-22-rbac-and-serviceaccounts โ€” this fails with Forbidden. Read the message carefully: it names the exact subject, verb, and resource that's missing.
  4. Before touching anything, confirm you understand why without needing a pod at all: kubectl auth can-i list pods --as=system:serviceaccount:lab-22-rbac-and-serviceaccounts:report-reader -n lab-22-rbac-and-serviceaccounts โ€” should print no.
  5. Create a Role granting get, list, watch on pods, and a RoleBinding granting that Role to the report-reader ServiceAccount, both in lab-22-rbac-and-serviceaccounts. Add them as a new file under manifests/ and apply it.
  6. Re-run the kubectl auth can-i command from step 4 โ€” it should now print yes.
  7. Re-run the kubectl exec ... kubectl get pods command from step 3 โ€” it should now succeed.

Check

Run the check once step 7 succeeds.

This lab runs against a real local Kubernetes cluster with an automated grader โ€” clone the repo and run make start to do it for real.

๐Ÿ“ Quiz

1. A pod needs to call the Kubernetes API from inside the cluster. What identity does it authenticate as by default, if you've set nothing?

2. What's the difference between a Role and a RoleBinding?

3. A pod's log shows: `Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:billing:worker" cannot list resource "pods" in API group "" in the namespace "billing"`. What's the minimal fix?scenario

4. What's the fastest way to check whether a ServiceAccount can perform an action, without touching a real pod?

5. Compared to attaching an IAM role to an EC2 instance, a Kubernetes ServiceAccount bound via a RoleBinding is closest to:scenario

Progress isn't saved in this preview โ€” run the course locally to track completion and grade labs for real.