CKA Kubernetes RBAC: Roles, Bindings, and ServiceAccounts
Introduction
Kubernetes RBAC controls which API operations users and workloads may perform. This lesson belongs to the Cluster Architecture, Installation & Configuration domain (25% of the CKA). The official curriculum asks you to manage role-based access control (RBAC). In the performance-based exam this almost always becomes a short, precise task: "create a Role that lets a ServiceAccount list Pods in namespace dev, bind it, and verify it", or "a Pod gets 403 Forbidden when it calls the API; fix the permissions". RBAC tasks are fast to solve if you know the four objects and three imperative commands by heart, and easy to lose points on if you confuse a namespaced Role with a cluster-wide ClusterRole.
After this lesson you will be able to create Roles, ClusterRoles, RoleBindings and ClusterRoleBindings imperatively and declaratively, grant permissions to users, groups and ServiceAccounts, attach a ServiceAccount to a Pod, and prove or debug access with kubectl auth can-i.
Learning objectives
- Explain how authentication, authorization and admission relate, and where RBAC fits.
- Write Roles and ClusterRoles with the right
apiGroups,resources,resourceNamesandverbs. - Bind them with RoleBindings and ClusterRoleBindings, and choose between the two correctly.
- Create ServiceAccounts, mount them into Pods, and request short-lived tokens.
- Verify and troubleshoot permissions with
kubectl auth can-i, including impersonation. - Avoid privilege escalation and over-broad grants.
How a request is authorized
API request processing includes authentication and authorization, followed by admission for applicable operations. Read-only requests do not run mutating/validating admission in the same way as writes:
- Authentication: who are you? Kubernetes has no
Userobject. Identities come from client certificates (the Common Name is the user, the Organization fields are groups), bearer tokens, OIDC tokens or ServiceAccount tokens. - Authorization: are you allowed to do this verb on this resource? The API server runs the configured authorizers (
--authorization-config, or the older--authorization-mode=Node,RBACflag on kubeadm clusters). RBAC is the standard one. Node authorization restricts what kubelets may read. - Admission: may the request be accepted, possibly after mutation or validation (LimitRanger, ResourceQuota, Pod Security, and so on)?
RBAC is additive and deny-by-default: there are no deny rules. In the RBAC authorizer, a matching rule must grant the action. Other configured authorizers and privileged groups can also affect the final API decision; an RBAC denial is not a universal deny across all configurations. Because you can only add permissions, to remove access you delete or edit the binding, never "add a deny".
The four RBAC objects
| Object | Scope | Purpose |
|---|---|---|
Role |
One namespace | Set of permissions on namespaced resources in that namespace |
ClusterRole |
Cluster | Permissions on cluster-scoped resources (nodes, PersistentVolumes, namespaces), on non-resource URLs (/healthz), or reusable across namespaces |
RoleBinding |
One namespace | Grants a Role or ClusterRole inside that namespace to subjects |
ClusterRoleBinding |
Cluster | Grants a ClusterRole in every namespace (and on cluster-scoped resources) to subjects |
Two points trip up many candidates:
- A RoleBinding may reference a ClusterRole. The permissions are then limited to the namespace of the binding. This is the idiomatic way to reuse one set of rules (such as the built-in
viewClusterRole) in many namespaces. - A RoleBinding can never reference a Role in another namespace, and a Role can never grant access to cluster-scoped resources.
Roles and ClusterRoles
A rule has three parts: which API groups, which resources, which verbs.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: [""] # "" is the core group (pods, services, configmaps, secrets...)
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]Facts to memorize:
- Core group is the empty string
"". Deployments are inapps, Jobs inbatch, Ingresses innetworking.k8s.io, StorageClasses instorage.k8s.io, Roles inrbac.authorization.k8s.io. - Verbs:
get,list,watch,create,update,patch,delete,deletecollection. Special verbs includeimpersonate,bind, andescalate. PodSecurityPolicy was removed in v1.25 and is not the current Pod Security Admission mechanism. - Subresources are separate resources:
pods/log,pods/exec,deployments/scale. Being able toget podsdoes not let you read logs or exec. resourceNamesrestricts a rule to named objects, for example one Secret or one ConfigMap. For restricted list/watch rules, the client must send a matching metadata.name field selector. It cannot restrict top-level create or deletecollection by name; named subresource operations such as pods/exec are a different case.nonResourceURLs(ClusterRole only) such as/healthzor/metrics.- Wildcards
"*"are allowed but dangerous; use them only in deliberate admin roles.
Creating them imperatively (fast in the exam)
kubectl create role pod-reader --verb=get,list,watch --resource=pods,pods/log -n dev
kubectl create clusterrole node-reader --verb=get,list,watch --resource=nodes
kubectl create role cm-editor --verb=get,update --resource=configmaps \
--resource-name=app-config -n devAdd --dry-run=client -o yaml to generate the manifest when you need to edit it (for instance to add several apiGroups).
Aggregated ClusterRoles
A ClusterRole with an aggregationRule collects the rules of every ClusterRole whose labels match a selector. The built-in admin, edit and view roles use this, so a CustomResourceDefinition author can make their resources visible to view by labelling a ClusterRole with rbac.authorization.k8s.io/aggregate-to-view: "true". The controller manager fills the rules automatically; you never edit the aggregated rules by hand.
Bindings
A binding links subjects to a roleRef.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: dev
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: build-bot
namespace: dev
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioImperative equivalents:
kubectl create rolebinding read-pods --role=pod-reader --user=jane \
--group=dev-team --serviceaccount=dev:build-bot -n dev
kubectl create clusterrolebinding read-nodes --clusterrole=node-reader --user=jane
kubectl create rolebinding view-in-qa --clusterrole=view --group=dev-team -n qaRules about bindings:
roleRefis immutable. To point a binding at a different role, delete and recreate it. You can editsubjectsfreely.- The
--serviceaccountflag takesnamespace:name. Specify the ServiceAccount subject namespace explicitly in YAML, especially for cross-namespace grants. It is required in a ClusterRoleBinding; User and Group subjects are not namespaced. - A ClusterRoleBinding to the
viewClusterRole gives read access to almost everything in all namespaces. The same ClusterRole through a RoleBinding limits it to one namespace.
Built-in roles worth knowing
| ClusterRole | What it grants |
|---|---|
cluster-admin |
Superuser RBAC permissions when bound cluster-wide; a RoleBinding scopes its applicable permissions to that namespace |
admin |
Full control inside a namespace, including creating Roles and RoleBindings there |
edit |
Read/write most namespaced objects, but not Roles or RoleBindings |
view |
Read-only on most namespaced objects, excluding Secrets, Roles, and RoleBindings; inspect aggregated rules for extensions |
Use a built-in role when its actual rules match the requirement; use a narrower custom role for exact resource lists. Do not assume view covers every resource. The system:masters group is an authorization bypass and must not be used as an ordinary team identity.
Worked example: scoped access for a contractor
Scenario. Contoso hires a contractor, user maria, to debug the payments namespace. She must read Pods and their logs and run kubectl exec into Pods, but she must not receive direct Secret API access or authorization in other namespaces. Exec access can still expose mounted Secrets and the workload identity; the example assumes the approved debug Pods contain no sensitive credentials.
Reasoning.
- The permissions are namespaced, so a Role in
paymentsis enough; no ClusterRole is needed. - Reading Pods and logs:
pods(get, list, watch) andpods/log(get). - Exec uses pods/exec. POST/SPDY requires create; WebSocket GET also has an initial get check and, by default since v1.35, an additional create authorization check. Grant get and create for this cross-transport debug example rather than assuming exec is always a POST.
- Secrets are absent from this Role. Check all of Maria’s other bindings and groups because grants are additive. A no-Secret-API rule does not prevent reading a mounted Secret from inside a container.
Three rules in one Role are clearest as a manifest:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: debugger
namespace: payments
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["get", "create"]kubectl create rolebinding maria-debugger --role=debugger --user=maria -n payments
kubectl auth can-i create pods --subresource=exec -n payments --as=maria # yes
kubectl auth can-i get pods --subresource=exec -n payments --as=maria # yes
kubectl auth can-i get secrets -n payments --as=maria # no
kubectl auth can-i list pods -n default --as=maria # noWhy not the alternatives? Binding the built-in edit role would let Maria read Secrets and modify workloads. A ClusterRoleBinding would expose every namespace. Granting * on pods* is not valid syntax for subresources and is too wide anyway.
ServiceAccounts
A ServiceAccount is an identity for processes running in Pods. Each namespace has a default ServiceAccount that every Pod uses unless it sets spec.serviceAccountName. Under default RBAC bindings, the default account has no application-specific grants. Actual permissions may come from other bindings; inspect them rather than relying on the account’s name.
kubectl create serviceaccount build-bot -n dev
kubectl run tester --image=registry.k8s.io/pause:3.10 -n dev \
--overrides='{"spec":{"serviceAccountName":"build-bot"}}'In a manifest:
apiVersion: v1
kind: Pod
metadata:
name: tester
namespace: dev
spec:
serviceAccountName: build-bot
automountServiceAccountToken: true
containers:
- name: pause
image: registry.k8s.io/pause:3.10This minimal Pod demonstrates identity attachment and token projection; it does not include kubectl or an interactive shell. Use an approved tools image when a task needs in-container API commands.
Key behaviours on modern clusters (v1.24 and later):
- Pods receive a projected, time-limited, audience-bound token (requested lifetime and actual expiry depend on cluster settings; refreshed by the kubelet) mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token, together withca.crtandnamespace. It is not backed by a Secret. - Long-lived token Secrets are no longer auto-created. If a legacy workflow needs one, you create a Secret of type
kubernetes.io/service-account-tokenannotated with the ServiceAccount name. - For a shell token, run
kubectl create token build-bot -n dev --duration=10m. This requests ten minutes; the server may issue a different lifetime. Pod/object binding requires explicit binding options; this command is not automatically bound to the tester Pod. Treat its output as a secret. - Set
automountServiceAccountToken: falseon the ServiceAccount or the Pod for workloads that never call the API. This reduces the damage if the container is compromised. - Inside the cluster, the username of a ServiceAccount is
system:serviceaccount:<namespace>:<name>, and its groups includesystem:serviceaccountsandsystem:serviceaccounts:<namespace>. Binding to the group grants a role to every ServiceAccount in that namespace.
Pulling private images uses imagePullSecrets on the ServiceAccount, which is a different feature from RBAC.
Worked example: a Pod that gets 403
Scenario. A monitoring Pod in namespace ops runs a script that does kubectl get pods -n ops. It fails with:
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:ops:default"
cannot list resource "pods" in API group "" in the namespace "ops"Reasoning. The message tells you everything: identity system:serviceaccount:ops:default, verb list, resource pods, group "", namespace ops. The error shows that the default account lacks this particular list permission in the active authorization configuration. Best practice is a dedicated ServiceAccount, not widening default.
kubectl create serviceaccount monitor -n ops
kubectl create role pod-lister --verb=list,get --resource=pods -n ops
kubectl create rolebinding monitor-pods --role=pod-lister --serviceaccount=ops:monitor -n ops
kubectl set serviceaccount deployment/monitoring monitor -n ops
kubectl auth can-i list pods -n ops --as=system:serviceaccount:ops:monitor # yeskubectl set serviceaccount changes the pod template, so the Deployment rolls out new Pods that use the new identity. Editing the ServiceAccount of a bare Pod is not possible; it must be recreated.
Why the other choices are worse. Binding cluster-admin to default would fix the error but give Pods using that account full cluster control. A ClusterRoleBinding would work but over-grants. Using ops:default with a Role is valid yet couples all Pods in the namespace to the same rights.
Verifying access with kubectl auth can-i
kubectl auth can-i asks the API server's authorization layer directly, so it is the fastest check after any RBAC change.
kubectl auth can-i create deployments -n dev # as yourself
kubectl auth can-i '*' '*' # broad authorization check, not proof of a particular binding
kubectl auth can-i list pods -n dev --as=jane # impersonate a user
kubectl auth can-i list pods -n dev --as=jane --as-group=dev-team
kubectl auth can-i get pods --subresource=log -n dev --as=system:serviceaccount:dev:build-bot
kubectl auth can-i --list -n dev --as=jane # everything jane may doImpersonation (--as) requires that your identity may impersonate users, groups or ServiceAccounts; a cluster-admin can. The answer is yes or no (exit code 0 or 1), and --list prints a table of resources, verbs and resourceNames. A can-i result tests authorization, not admission, object existence, network connectivity, or all application effects. Include required groups when reproducing group-based grants; --as alone does not reproduce every original identity context. The following binding searches are quick hints, not a complete audit of group grants or every subject:
kubectl get rolebindings,clusterrolebindings -A -o wide | grep jane
kubectl describe clusterrole view
kubectl get clusterrolebinding -o jsonpath='{range .items[?(@.subjects[0].name=="jane")]}{.metadata.name}{"\n"}{end}'Users via certificates (a common exam task)
Because Kubernetes has no User object, "create a user" means issuing a credential. The standard path:
- Generate a key and a CSR whose subject is
/CN=jane/O=dev-team. - Submit a
CertificateSigningRequestobject withsignerName: kubernetes.io/kube-apiserver-client,usages: ["client auth"], and the base64 CSR. - Approve it:
kubectl certificate approve jane. - Retrieve the certificate from
status.certificate, then add credentials and a context withkubectl config set-credentialsandset-context. - Grant permissions with a RoleBinding to user
janeor groupdev-team.
A client certificate establishes the user and groups; their existing bindings or privileged memberships determine access. Avoid privileged organizations such as system:masters. Validate the CSR subject and requester before approval, and retrieve an issued certificate only after the configured signer has signed it. For local CA renewal, see kubeadm certificate management and cluster recovery.
Preventing privilege escalation
The API server blocks a user from creating or updating a Role or binding that contains permissions the user does not already hold, unless they also have the escalate verb on roles or the bind verb on the referenced role. This is why granting create rolebindings carelessly is dangerous: with bind on cluster-admin a user can promote themselves. Also treat these grants as sensitive: get or list on Secrets (can disclose stored credentials, but projected ServiceAccount tokens are not stored in Secret objects), create pods (a Pod can mount other ServiceAccounts' tokens in the same namespace), impersonate, and wildcards.
Workload access also depends on how ConfigMaps and Secrets are supplied to Pods; RBAC alone does not isolate data already mounted into an allowed workload.
Comparison: which object do I need?
| Requirement | Use |
|---|---|
Read Pods only in dev |
Role + RoleBinding |
| Reuse the same rules in 20 namespaces | One ClusterRole, a RoleBinding per namespace |
| Access nodes, PersistentVolumes, namespaces | ClusterRole + ClusterRoleBinding |
| Same permissions in all namespaces | ClusterRole + ClusterRoleBinding |
Allow /healthz or /metrics URLs |
ClusterRole with nonResourceURLs |
| Give one Pod an API identity | ServiceAccount + binding + serviceAccountName |
Common pitfalls and exam tips
- Wrong scope. Nodes, PVs and Namespaces are cluster-scoped; a Role cannot grant them. Check with
kubectl api-resources --namespaced=false. - Forgetting the subresource. Logs, exec, port-forward and scale are separate resources; a missing
pods/logis a classic cause ofForbidden. - Wrong
apiGroups. Deployments needapps, not"". If unsure, runkubectl api-resources | grep -i deploymentand read theAPIVERSIONcolumn. - ServiceAccount subject scope. Explicitly name its namespace in YAML; a ClusterRoleBinding requires it, while a namespaced RoleBinding can resolve an omitted namespace relative to itself. The CLI --serviceaccount flag uses namespace:name.
- Editing
roleRef. It is immutable; delete and recreate. - Verifying as the wrong identity.
kubectl auth can-iwithout--astests your admin rights and will sayyes. Always add--as. - Exam tip: use
kubectl create role|clusterrole|rolebinding|clusterrolebindingwith-hfor examples, and finish every RBAC task with anauth can-i --as=...check. Switch to the correct context first (the exam gives you akubectl config use-contextline for each task) and always pass-n. - Exam tip: read the question for the exact names. Typos in a role name silently produce a binding that points at nothing;
kubectl describe rolebindingreveals it only if you look.
Mini case study: separating teams at Northwind
Northwind runs one cluster with namespaces web, data and ops. Requirements:
- Group
web-devscan do anything to Deployments, Services and ConfigMaps inweb, but nothing in other namespaces. - Group auditors can use the default view permissions across namespaces, excluding Secrets and RBAC configuration; additional custom-resource coverage requires explicit rules.
- The CI job, ServiceAccount
deployerinops, must be able to update Deployments inwebonly.
Solution.
- A namespaced Role for
web-devsand a RoleBinding:
kubectl create role web-admin -n web --verb='*' --resource=deployments.apps,services,configmaps
kubectl create rolebinding web-devs-admin -n web --role=web-admin --group=web-devsThis Role grants no direct Secret API access or permissions in data or ops. However, permission to create or modify Deployments can expose Secrets or stronger ServiceAccounts through the Pods they create; use admission policy and workload constraints when stronger isolation is required.
- The built-in view ClusterRole covers most namespaced application resources and excludes Secrets, Roles, and RoleBindings. Binding it with a ClusterRoleBinding applies it in every namespace:
kubectl create clusterrolebinding auditors-view --clusterrole=view --group=auditors- The identity lives in
opsbut the permission is needed inweb. A RoleBinding inwebcan name a ServiceAccount from another namespace:
kubectl create role deploy-updater -n web --verb=get,list,patch,update --resource=deployments.apps
kubectl create rolebinding ci-deployer -n web --role=deploy-updater --serviceaccount=ops:deployer
kubectl auth can-i patch deployments.apps -n web --as=system:serviceaccount:ops:deployer # yes
kubectl auth can-i patch deployments.apps -n data --as=system:serviceaccount:ops:deployer # noCommentary. Requirement 3 is the subtle one: where the ServiceAccount lives and where the permission applies are independent. The RoleBinding's namespace decides where it applies; the subject's namespace field identifies the account.
When installing Helm charts and operators, inspect the Roles and bindings they request, especially cluster-wide grants and permissions to read credentials.
Key terms
- Role / ClusterRole: a set of permission rules, namespaced or cluster-wide.
- RoleBinding / ClusterRoleBinding: attaches a role to users, groups or ServiceAccounts.
- Subject: a User, Group or ServiceAccount receiving permissions.
- Verb: the action on a resource (
get,list,create...). - Subresource: an extra endpoint of a resource, for example
pods/log. - ServiceAccount: identity used by Pods.
- Impersonation: acting as another identity with
--asfor testing. - Aggregated ClusterRole: a role built from the rules of labelled ClusterRoles.
Summary checklist
- RBAC is additive and deny-by-default; there are no deny rules.
- Role and RoleBinding for one namespace; ClusterRole and ClusterRoleBinding for cluster-scoped resources or all namespaces.
- A RoleBinding to a ClusterRole limits it to the binding's namespace.
- Subresources (
pods/log,pods/exec) and API groups (apps,"") must be listed explicitly. -
roleRefis immutable;--serviceaccounttakesnamespace:name. - Pods use
serviceAccountName; tokens are projected and short-lived;kubectl create tokenfor a manual token. - Verify with
kubectl auth can-i <verb> <resource> -n <ns> --as=<identity>.
Official sources
- Using RBAC authorization: https://kubernetes.io/docs/reference/access-authn-authz/rbac/ (opens in a new tab)
- Managing service accounts: https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/ (opens in a new tab)
- Configure service accounts for Pods: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/ (opens in a new tab)
- Certificates and certificate signing requests: https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/ (opens in a new tab)
- RBAC good practices: https://kubernetes.io/docs/concepts/security/rbac-good-practices/ (opens in a new tab)
- kubectl auth can-i and subresources: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_auth/kubectl_auth_can-i/ (opens in a new tab)
- TokenRequest duration and binding: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_create/kubectl_create_token/ (opens in a new tab)
- CKA curriculum: https://github.com/cncf/curriculum/tree/master/cka (opens in a new tab)
