| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKA · CKAD |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | Role and ClusterRole rules, RoleBinding and ClusterRoleBinding subjects, kubectl auth can-i verification, apps API group grants, ClusterRole in a namespaced RoleBinding, node read access, and pods/log subresources. Does not cover authentication setup, admission control, certificate creation, cluster-admin shortcuts, aggregated ClusterRoles, or authorization webhooks. |
| Related guides | kubectl apply, patch and replace kubectl logs, Events and describe |
RBAC separates what is allowed from who receives the grant:
- A Role or ClusterRole lists API verbs on resources
- A binding connects that rule set to a user, group, or ServiceAccount
This walkthrough builds a read-only Pod role in namespace development, verifies it with kubectl auth can-i, then extends the same pattern to Deployments, cluster-scoped Nodes, and Pod subresources.
Creating ClusterRoles and ClusterRoleBindings requires cluster-scoped authorization. Every --as=system:serviceaccount:... check also requires the current kubeconfig identity to have ServiceAccount impersonation permission.
Understand Kubernetes RBAC
Role, ClusterRole, and binding scope
Four objects cover most exam and day-to-day tasks:
| Object | Scope | Purpose |
|---|---|---|
| Role | Namespace | Defines permissions in one namespace |
| ClusterRole | Cluster | Defines reusable or cluster-scoped permissions |
| RoleBinding | Namespace | Grants a Role or ClusterRole inside one namespace |
| ClusterRoleBinding | Cluster | Grants a ClusterRole cluster-wide |
Permissions and subjects stay separate:
- Role or ClusterRole = what is allowed
- Binding = who receives it and where
Authentication must succeed before RBAC runs. For how identity reaches the API server, see authentication and admission control. For ServiceAccount creation and token mounts, see Kubernetes ServiceAccounts.
API groups, resources, verbs, and subresources
A typical namespaced rule looks like this:
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]- Core API resources such as Pods and Services use an empty string in
apiGroups. - Named groups include
appsfor Deployments,rbac.authorization.k8s.iofor Roles, and others. resourcesuse plural API names (pods, notpod).verbsare Kubernetes authorization verbs (get,list,create,delete, …), not always one-to-one with HTTP methods.
Confirm group and resource names on your cluster:
kubectl api-resources | grep -E '^(NAME|pods|deployments|nodes) 'Sample output:
NAME SHORTNAMES APIVERSION NAMESPACED KIND
nodes no v1 false Node
pods po v1 true Pod
deployments deploy apps/v1 true DeploymentPods sit in the core group with NAMESPACED true. Nodes are cluster-scoped. Deployments belong to apps/v1.
Create a Namespaced Role and RoleBinding
Create the ServiceAccount and read-only Role
Create the lab namespace:
kubectl create namespace developmentSample output:
namespace/development createdCreate a Role that allows read-only Pod access:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: development
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]Apply the Role:
kubectl apply -f pod-reader-role.yamlSample output:
role.rbac.authorization.k8s.io/pod-reader createdThe Role exists but grants nothing until you bind it to a subject. Create a dedicated ServiceAccount:
kubectl create serviceaccount pod-reader -n developmentSample output:
serviceaccount/pod-reader createdBind the Role to that account:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: development
subjects:
- kind: ServiceAccount
name: pod-reader
namespace: development
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioApply the binding:
kubectl apply -f pod-reader-binding.yamlSample output:
rolebinding.rbac.authorization.k8s.io/pod-reader-binding createdInspect what the binding connects:
kubectl describe rolebinding pod-reader-binding -n developmentSample output:
Name: pod-reader-binding
Role:
Kind: Role
Name: pod-reader
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount pod-reader developmentThe ServiceAccount subject needs all three fields: kind, name, and namespace.
Verify permissions with kubectl auth can-i
These commands use API impersonation. They evaluate authorization as the named ServiceAccount but do not test its token. Your current kubeconfig identity must be allowed to impersonate ServiceAccounts.
Impersonate the ServiceAccount API identity:
kubectl auth can-i get pods --as=system:serviceaccount:development:pod-reader -n developmentSample output:
yesTest a verb that the Role does not grant:
kubectl auth can-i create pods --as=system:serviceaccount:development:pod-reader -n developmentSample output:
noList effective permissions in the namespace:
kubectl auth can-i --list --as=system:serviceaccount:development:pod-reader -n developmentSample output (trimmed):
Resources Verbs
pods [get list watch]The table may also list cluster-default permissions your CNI or platform adds. Focus on the pods row for this Role.
Add Deployment access
Extend the same Role with Deployment read access. Save the complete updated manifest in pod-reader-role.yaml:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: development
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]Apply the updated Role:
kubectl apply -f pod-reader-role.yamlSample output:
role.rbac.authorization.k8s.io/pod-reader configuredVerify Deployment access:
kubectl auth can-i get deployments --as=system:serviceaccount:development:pod-reader -n developmentSample output:
yesPod access remains unchanged:
kubectl auth can-i get pods --as=system:serviceaccount:development:pod-reader -n developmentSample output:
yesDeployment permissions do not imply Pod permissions, and the reverse is also true. Grant each resource explicitly.
Reuse a ClusterRole in One Namespace
A ClusterRole can hold reusable rules. A RoleBinding limits where those rules apply. Do not reuse pod-reader here—its existing RoleBinding already grants the tested Pod access.
Save a separate ServiceAccount and binding in reusable-pod-reader.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: reusable-pod-reader
namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cluster-pod-reader-binding
namespace: development
subjects:
- kind: ServiceAccount
name: reusable-pod-reader
namespace: development
roleRef:
kind: ClusterRole
name: cluster-pod-reader
apiGroup: rbac.authorization.k8s.ioApply the manifest:
kubectl apply -f reusable-pod-reader.yamlThen test the isolated identity:
kubectl auth can-i get pods --as=system:serviceaccount:development:reusable-pod-reader -n developmentSample output:
yesThe same identity cannot read Pods in default:
kubectl auth can-i get pods --as=system:serviceaccount:development:reusable-pod-reader -n defaultSample output:
noA RoleBinding can reference a ClusterRole, but it grants the namespaced portions of that ClusterRole only within the RoleBinding's namespace.
Grant Cluster-Wide Access
Create a Node reader
Cluster-scoped resources such as Nodes need a ClusterRole and ClusterRoleBinding. Save all three objects in node-reader-rbac.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: node-reader
namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-reader-binding
subjects:
- kind: ServiceAccount
name: node-reader
namespace: development
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.ioApply the manifest:
kubectl apply -f node-reader-rbac.yamlSample output:
serviceaccount/node-reader created
clusterrole.rbac.authorization.k8s.io/node-reader created
clusterrolebinding.rbac.authorization.k8s.io/node-reader-binding createdTest cluster-scoped and namespaced permissions
Test node read access:
kubectl auth can-i get nodes --as=system:serviceaccount:development:node-readerSample output:
yesThe node-reader identity has no Pod access in development:
kubectl auth can-i get pods --as=system:serviceaccount:development:node-reader -n developmentSample output:
noA ClusterRoleBinding cannot reference a namespaced Role. Use ClusterRole for cluster-wide grants.
Authorize Pod Logs and Exec Separately
Reading Pod objects does not automatically allow kubectl logs or kubectl exec. Those use subresources. Keep pods and pods/log as separate rule entries so the verbs for the main resource and subresource remain explicit.
Save the ServiceAccount, Role, and RoleBinding in log-reader-rbac.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: log-reader
namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: log-reader
namespace: development
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: log-reader
namespace: development
subjects:
- kind: ServiceAccount
name: log-reader
namespace: development
roleRef:
kind: Role
name: log-reader
apiGroup: rbac.authorization.k8s.ioApply the manifest:
kubectl apply -f log-reader-rbac.yamlThen test:
kubectl auth can-i get pods/log --as=system:serviceaccount:development:log-reader -n developmentSample output:
yesExec requires its own grant. The log-reader Role does not include it:
kubectl auth can-i create pods/exec --as=system:serviceaccount:development:log-reader -n developmentSample output:
noBecause this Role also lists pods, the same identity can get Pod objects. A tighter Role with only pods/log would allow log access without listing Pods.
Bind Users and Groups
Bindings accept any authenticated subject. RBAC references the identity; it does not create it.
User subject:
subjects:
- kind: User
name: jane
apiGroup: rbac.authorization.k8s.ioGroup subject:
subjects:
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.ioThe name must match what your authentication method returns after the API server validates credentials.
Modify and Troubleshoot RBAC
Immutable roleRef
- You can update
ruleson a Role or ClusterRole in place. roleRefon a RoleBinding or ClusterRoleBinding is immutable.- To point a binding at a different Role, delete the binding and recreate it.
- You can add or remove subjects on an existing binding.
Do not use cluster-admin as a routine fix. Grant the narrowest Role or ClusterRole that satisfies the task.
Forbidden-error workflow
When the API returns 403 Forbidden, work through this sequence:
- Read the exact user, verb, resource, and namespace in the error message.
- Confirm which identity the caller uses (kubeconfig user or
system:serviceaccount:...). - Run
kubectl auth can-iwith--asfor that identity. - List RoleBindings and ClusterRoleBindings that reference the subject.
- Inspect
ruleson the referenced Role or ClusterRole. - Check API group, subresource (
pods/log), and namespace scope. - Re-test after you apply the correction.
Common scope and API-group mistakes
| Mistake | What goes wrong |
|---|---|
| Wrong API group | Rule matches nothing; use kubectl api-resources |
| Singular resource name | Use pods, not pod |
| RoleBinding in wrong namespace | Grant applies only where the binding lives |
ServiceAccount subject missing namespace |
Binding does not match the intended account |
| ClusterRole created but never bound | Rules exist with no subject |
| RoleBinding expected to span namespaces | Create a RoleBinding in each intended namespace, optionally referencing one reusable ClusterRole. Use ClusterRoleBinding only when the grant should apply cluster-wide |
Pod access assumed to include pods/log |
Add pods/log explicitly for log access |
| Permission granted to wrong username | can-i passes for admin but fails for the workload SA |
RoleBindings remain limited to their own namespace even when they reference a ClusterRole.
What's Next
- Kubernetes Authentication, Authorization and Admission Control
- Kubernetes ServiceAccounts with Examples
- Deploy Applications on Kubernetes with Helm
References
- Using RBAC authorization — Kubernetes documentation
- Authorization — authorization overview
- kubectl auth can-i — generated command reference
Summary
RBAC pairs rule objects with bindings. A Role or ClusterRole lists apiGroups, resources, and verbs. A RoleBinding or ClusterRoleBinding connects those rules to a user, group, or ServiceAccount in a specific scope. Namespace grants use Role plus RoleBinding; cluster-wide grants use ClusterRole plus ClusterRoleBinding.
The lab walked from a read-only Pod Role in development through kubectl auth can-i checks, an apps Deployment rule, a ClusterRole limited by RoleBinding to one namespace, and a node-reader ClusterRoleBinding for cluster-scoped reads. Subresources such as pods/log need their own resource entries; Pod read access does not imply log or exec access.
When troubleshooting, read the forbidden message, impersonate the caller with --as=system:serviceaccount:namespace:name, and trace bindings back to their rules. Remember that roleRef is immutable—update rules on the Role itself, or recreate the binding when you need a different target.

