Skip to content

Kubernetes RBAC, Roles, Bindings, and ServiceAccounts

Step 5 of 24

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, resourceNames and verbs.
  • 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:

  1. Authentication: who are you? Kubernetes has no User object. Identities come from client certificates (the Common Name is the user, the Organization fields are groups), bearer tokens, OIDC tokens or ServiceAccount tokens.
  2. 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,RBAC flag on kubeadm clusters). RBAC is the standard one. Node authorization restricts what kubelets may read.
  3. 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 view ClusterRole) 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.

YAML
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 in apps, Jobs in batch, Ingresses in networking.k8s.io, StorageClasses in storage.k8s.io, Roles in rbac.authorization.k8s.io.
  • Verbs: get, list, watch, create, update, patch, delete, deletecollection. Special verbs include impersonate, bind, and escalate. 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 to get pods does not let you read logs or exec.
  • resourceNames restricts 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 /healthz or /metrics.
  • Wildcards "*" are allowed but dangerous; use them only in deliberate admin roles.

Creating them imperatively (fast in the exam)

Bash
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 dev

Add --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.

YAML
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.io

Imperative equivalents:

Bash
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 qa

Rules about bindings:

  • roleRef is immutable. To point a binding at a different role, delete and recreate it. You can edit subjects freely.
  • The --serviceaccount flag takes namespace: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 view ClusterRole 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.

  1. The permissions are namespaced, so a Role in payments is enough; no ClusterRole is needed.
  2. Reading Pods and logs: pods (get, list, watch) and pods/log (get).
  3. 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.
  4. 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:

YAML
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"]
Bash
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           # no

Why 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.

Bash
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:

YAML
apiVersion: v1
kind: Pod
metadata:
  name: tester
  namespace: dev
spec:
  serviceAccountName: build-bot
  automountServiceAccountToken: true
  containers:
  - name: pause
    image: registry.k8s.io/pause:3.10

This 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 with ca.crt and namespace. 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-token annotated 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: false on 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 include system:serviceaccounts and system: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:

Text
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.

Bash
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   # yes

kubectl 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.

Bash
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 do

Impersonation (--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:

Bash
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:

  1. Generate a key and a CSR whose subject is /CN=jane/O=dev-team.
  2. Submit a CertificateSigningRequest object with signerName: kubernetes.io/kube-apiserver-client, usages: ["client auth"], and the base64 CSR.
  3. Approve it: kubectl certificate approve jane.
  4. Retrieve the certificate from status.certificate, then add credentials and a context with kubectl config set-credentials and set-context.
  5. Grant permissions with a RoleBinding to user jane or group dev-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/log is a classic cause of Forbidden.
  • Wrong apiGroups. Deployments need apps, not "". If unsure, run kubectl api-resources | grep -i deployment and read the APIVERSION column.
  • 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-i without --as tests your admin rights and will say yes. Always add --as.
  • Exam tip: use kubectl create role|clusterrole|rolebinding|clusterrolebinding with -h for examples, and finish every RBAC task with an auth can-i --as=... check. Switch to the correct context first (the exam gives you a kubectl config use-context line 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 rolebinding reveals it only if you look.

Mini case study: separating teams at Northwind

Northwind runs one cluster with namespaces web, data and ops. Requirements:

  1. Group web-devs can do anything to Deployments, Services and ConfigMaps in web, but nothing in other namespaces.
  2. Group auditors can use the default view permissions across namespaces, excluding Secrets and RBAC configuration; additional custom-resource coverage requires explicit rules.
  3. The CI job, ServiceAccount deployer in ops, must be able to update Deployments in web only.

Solution.

  1. A namespaced Role for web-devs and a RoleBinding:
Bash
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-devs

This 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.

  1. 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:
Bash
kubectl create clusterrolebinding auditors-view --clusterrole=view --group=auditors
  1. The identity lives in ops but the permission is needed in web. A RoleBinding in web can name a ServiceAccount from another namespace:
Bash
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  # no

Commentary. 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 --as for 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.
  • roleRef is immutable; --serviceaccount takes namespace:name.
  • Pods use serviceAccountName; tokens are projected and short-lived; kubectl create token for a manual token.
  • Verify with kubectl auth can-i <verb> <resource> -n <ns> --as=<identity>.

Official sources