Lab 5: RBAC Authorization Deep Dive
Time: 50 minutes
Objective: Build and troubleshoot namespace and cluster RBAC policies using kubectl auth can-i and impersonation.
The Story
Most outages from access control are not "RBAC is broken"; they are scope mistakes, wrong subjects, or missing bindings discovered during an incident. This lab trains a repeatable deny-triage workflow so you can prove authorization outcomes quickly instead of guessing.
CKA Objectives Mapped
- Manage role-based access controls (RBAC)
- Use least privilege for users and service accounts
- Troubleshoot authorization denies quickly
Background: How RBAC Works
Kubernetes RBAC starts with identity, but identity works differently than AWS IAM. Kubernetes recognizes User, ServiceAccount, and Group, and only one of those is a first-class Kubernetes object: ServiceAccount inside a namespace. User identities usually come from client certificates or an external identity provider (OIDC), not from objects stored in the cluster, and kubectl normally uses whatever user identity is configured in your kubeconfig context.
When you create a ServiceAccount, Kubernetes automatically projects a short-lived token into any Pod that references it via spec.serviceAccountName. That token is mounted at /var/run/secrets/kubernetes.io/serviceaccount/token and carries the identity string system:serviceaccount:<namespace>:<name>. Every API call the Pod makes includes this token, and the API server resolves it to that identity string before evaluating RBAC rules. When you write a RoleBinding or ClusterRoleBinding with subjects[].kind: ServiceAccount, you are attaching permissions to that same identity string — which is why kubectl auth can-i uses --as=system:serviceaccount:rbac-lab:trainee to simulate it.
Roles and bindings are separate objects on purpose. A Role or ClusterRole defines what actions are allowed on which resources, with rule fields like apiGroups, resources, and verbs, while a RoleBinding or ClusterRoleBinding attaches that permission set to a specific subject through subjects and roleRef. This separation keeps permissions reusable and auditable because you can attach the same role to many identities without duplicating policy rules.
Scope is the next thing that trips people up. Role and RoleBinding are namespace-scoped, while ClusterRole and ClusterRoleBinding are cluster-scoped. A useful pattern is defining a reusable ClusterRole such as read-only pods, then using a namespaced RoleBinding to grant that access only in one namespace.
Kubernetes also supports ClusterRole aggregation, which lets you compose a ClusterRole automatically from other ClusterRoles that match a label selector. A ClusterRole with an aggregationRule field watches for ClusterRoles carrying specific labels and merges their rules into itself continuously — you never update the aggregated role directly. The built-in admin, edit, and view ClusterRoles already use this mechanism, which is why installing a Custom Resource Definition with matching labels can transparently extend those roles. For the CKA exam, know that aggregationRule exists, that it uses label selectors under clusterRoleSelectors, and that changes to source ClusterRoles propagate automatically without any manual reconciliation step.
kubectl auth can-i asks the API server to evaluate authorization exactly like a real request, so it is the fastest way to prove whether an action should be allowed. With --as=..., you impersonate another identity and test policy behavior without logging in as that principal. This is the same mental model as IAM policy simulation in AWS before you hand permissions to a real workload.
The most common RBAC mistake is crossing namespace boundaries with a RoleBinding. A RoleBinding in namespace A can only reference a Role that also lives in namespace A; pointing at a Role in another namespace does not grant what you expect. You will intentionally hit this in Part 4, so read failures as a scoping bug first, not as a syntax bug. For deeper reference, see the official Kubernetes RBAC docs: https://kubernetes.io/docs/reference/access-authn-authz/rbac/.
Prerequisites
Use your local kind cluster:
kubectl config use-context kind-lab
kubectl get nodesStarter assets for this lab are in starter/:
namespace-and-accounts.yamlrole-pod-reader.yamlrolebinding.yamlclusterrole-node-reader.yamlclusterrolebinding.yamlverify.sh
Part 1: Create a Sandbox Namespace and Identities
kubectl create namespace rbac-lab
kubectl -n rbac-lab create serviceaccount trainee
kubectl -n rbac-lab create serviceaccount auditorValidate identities exist:
kubectl -n rbac-lab get serviceaccountsReference: ServiceAccount docs | RBAC overview
Part 2: Namespace-Scoped Read Access
Create a Role that allows read-only pod access:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: rbac-lab
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
EOFBind trainee to the role:
kubectl -n rbac-lab create rolebinding trainee-pod-reader \
--role=pod-reader \
--serviceaccount=rbac-lab:traineeTest with impersonation:
kubectl auth can-i list pods --as=system:serviceaccount:rbac-lab:trainee -n rbac-lab
kubectl auth can-i delete pods --as=system:serviceaccount:rbac-lab:trainee -n rbac-labExpected:
list pods: yesdelete pods: no
Reference: Role and RoleBinding | kubectl auth can-i
Part 3: Cluster-Scoped Read Access
Create a ClusterRole for node visibility:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
EOFBind auditor to the ClusterRole:
kubectl create clusterrolebinding auditor-node-reader \
--clusterrole=node-reader \
--serviceaccount=rbac-lab:auditorVerify:
kubectl auth can-i list nodes --as=system:serviceaccount:rbac-lab:auditor
kubectl auth can-i list secrets --as=system:serviceaccount:rbac-lab:auditor -n rbac-labExpected:
- can list nodes: yes
- can list secrets: no
Reference: ClusterRole and ClusterRoleBinding | Aggregated ClusterRoles
Part 4: Break and Fix a Binding
Create a broken RoleBinding on purpose (wrong namespace target):
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: trainee-broken
namespace: default
subjects:
- kind: ServiceAccount
name: trainee
namespace: rbac-lab
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pod-reader
EOFRun checks and inspect:
kubectl auth can-i list pods --as=system:serviceaccount:rbac-lab:trainee -n default
kubectl -n default describe rolebinding trainee-brokenFix it by removing the bad binding:
kubectl -n default delete rolebinding trainee-brokenExplain why it failed:
- RoleBindings are namespace-scoped
roleRef: Rolemust reference a Role in the same namespace as the binding
Part 5: Triage Sequence You Should Memorize
When a user gets Forbidden, use this order:
- Confirm identity
kubectl auth can-i --list --as=system:serviceaccount:rbac-lab:trainee -n rbac-lab- Inspect bindings in the target namespace
kubectl -n rbac-lab get rolebindings
kubectl get clusterrolebindings | grep trainee || true- Inspect referenced role/clusterrole rules
kubectl -n rbac-lab describe role pod-reader
kubectl describe clusterrole node-readerVerification Checklist
You are done when:
traineecan list pods inrbac-labbut cannot delete themauditorcan list nodes but cannot read secrets- You can diagnose a broken binding and explain the root cause
Cleanup
kubectl delete namespace rbac-lab
kubectl delete clusterrole node-reader
kubectl delete clusterrolebinding auditor-node-readerReinforcement Scenarios
jerry-rbac-denied

