RBAC: Role and RoleBinding
The four RBAC objects, a field-by-field look at Role and RoleBinding, and kubectl auth can-i as a way to stop guessing at a 403.
The four RBAC objects, a field-by-field look at Role and RoleBinding, and
kubectl auth can-i as a way to stop guessing about a 403.
Why this matters
In the last lesson, the Pod got its own identity, a ServiceAccount. By itself it grants no permissions: the apiserver knows who's asking, but denies everything. What's actually allowed is decided by RBAC.
It's easy to get this wrong in both directions. Miss one word in a rule and
the app fails with a 403. Grant too much, say the built-in cluster-admin
"just to make it work", and breaching one Pod opens up the whole cluster.
Four RBAC objects
RBAC (role-based access control) in Kubernetes rests on four kinds:
- Role: a set of permissions inside a single namespace.
- ClusterRole: the same, but at the cluster level (or for cluster-wide resources like nodes, persistentvolumes).
- RoleBinding: attaches a Role or ClusterRole to a subject (ServiceAccount, User, Group) in a specific namespace.
- ClusterRoleBinding: attaches a ClusterRole to the whole cluster.
The usual combination, "grant a ServiceAccount these permissions on these resources in this namespace," is built from the first three: a Role describes the permissions, a RoleBinding grants them to a specific SA.
RBAC permissions only add up. There are no deny rules: anything not granted by some binding is denied (docs).
Role: what it lets you do
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: demo
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
Breaking down the fields:
apiGroups: [""]. An empty string means "core API group" (Pod, Service, ConfigMap, Secret, Namespace, and so on). For Deployment it would be["apps"], for Ingress["networking.k8s.io"]. You can see a resource's group withkubectl api-resources, theAPIVERSIONcolumn.resources: ["pods", "pods/log"]. These are the resources the permissions apply to. Names are written as they appear in theNAMEcolumn ofkubectl api-resources. Kubernetes won't notice a typo: the Role gets created, but its permissions just won't fire.pods/logis a subresource, handled separately frompodsitself. Without it,kubectl logsfails even when Pods are visible.verbs: which operations. The main list:get,list,watch,create,update,patch,delete,deletecollection. A star*means all of them.
A Role can hold several rules, each with its own set of
apiGroups/resources/verbs.
RoleBinding: who gets it
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-sa-can-read
namespace: demo
subjects:
- kind: ServiceAccount
name: app-sa
namespace: demo
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
subjects is an array of who gets the permissions. Kinds: ServiceAccount,
User, Group. For a ServiceAccount, you give the namespace it lives in.
In a RoleBinding, if that SA namespace is left empty the apiserver reads
it as the binding's own namespace. In a ClusterRoleBinding, without it the
object won't even get created: subjects[0].namespace: Required value.
A typo in the SA's name or its namespace gives no sign of itself: the
RoleBinding gets created and grants permissions to a subject that doesn't
exist. For example, namespace: default instead of demo. You check this
with the command
kubectl auth can-i get pods -n demo --as=system:serviceaccount:demo:app-sa.
roleRef defines exactly what's granted. You can't change your mind here
after creation: the field is immutable, so changing the role means
deleting and recreating the RoleBinding.
Where ServiceAccounts live
ServiceAccount was covered in the last lesson. Briefly: without
spec.serviceAccountName, a Pod runs under SA default of its own
namespace, and the SA's token sits at
/var/run/secrets/kubernetes.io/serviceaccount/. That's where the app
reads it to talk to the API server.
Create your own SA:
kubectl create serviceaccount app-sa -n demo
Attach it to a Pod:
spec:
serviceAccountName: app-sa
kubectl auth can-i: your main diagnostic tool
Before guessing "why is it 403," it's worth asking the API server directly:
kubectl auth can-i get pods
kubectl auth can-i delete pods
This command answers for the current user. To check on behalf of another
subject, use the --as flag (demo here is the namespace the SA lives
in):
kubectl auth can-i get pods -n demo --as=system:serviceaccount:demo:app-sa
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app-sa
A yes or no answer with no lengthy investigation
(docs).
A resource from another group is written with the group after a dot:
kubectl auth can-i list deployments.apps -n demo --as=....
The text of a 403 also tells you what's missing. It has the subject, verb, resource, and group:
Error from server (Forbidden): deployments.apps is forbidden: User "system:serviceaccount:demo:app-sa" cannot list resource "deployments" in API group "apps" in the namespace "demo"
Role vs ClusterRole: when to use which
- Access only to resources inside a single namespace: Role.
- Access to namespaced resources across several namespaces: ClusterRole plus a RoleBinding in each of them.
- Access to cluster-scoped resources (nodes, namespaces, persistentvolumes, customresourcedefinitions): only ClusterRole plus ClusterRoleBinding. A RoleBinding won't work on these resources.
| Role and binding | Where the permissions apply |
|---|---|
| Role + RoleBinding | resources in one namespace |
| ClusterRole + RoleBinding | namespaced resources, only in that namespace |
| ClusterRole + ClusterRoleBinding | the whole cluster, including nodes, PV, CRD |
A common mistake: create a Role on nodes in some namespace and get
denied anyway. Kubernetes accepts such a Role with no error, but nodes are
a cluster-scoped resource: they don't exist at the namespace level at all.
ClusterRole with a RoleBinding most often shows up with the built-in
roles. view gives read access to most objects in a namespace, except
Secret, Role and RoleBinding. edit adds write access, including Secret.
admin also adds managing Role and RoleBinding in the namespace
(docs).
Grant view to one SA in one namespace:
kubectl create rolebinding app-sa-view -n demo \
--clusterrole=view --serviceaccount=demo:app-sa
Common mistakes
- Bound a ClusterRole through a RoleBinding and found that cluster-scoped resources aren't visible. A RoleBinding limits a ClusterRole to just the namespaced part, for the current namespace only.
- Forgot
pods/login resources:kubectl logsfails with a 403, whileget podsworks. - Described a resource from the
appsgroup (Deployment, StatefulSet, DaemonSet) in a rule withapiGroups: [""]. The Role gets created, the permissions don't work, and the 403 text showsin API group "apps". - Checked a cluster-scoped resource with a plain
kubectl auth can-i list nodes --as=.... kubectl still fills in a namespace, from-nor from the kubeconfig context. Then a Role on nodes in that namespace answersyes, even thoughkubectl get nodesgets a 403. kubectl only warnsresource 'nodes' is not namespace scoped. For nodes, namespaces and PV, ask with-A(kubectl auth can-i list nodes -A --as=...) or make a real request,kubectl get nodes --as=.... - Wanted to change
roleRef, kubectl answeredThe RoleBinding "x" is invalid: roleRef: Invalid value: ...: cannot change roleRef. Delete it and create it again. - Fixed a 403 by granting
editorcluster-admin. The error goes away, but the app gets write access, Secrets, or the whole cluster.
Related commands
# Create SA, Role, RoleBinding imperatively
kubectl create serviceaccount app-sa -n demo
kubectl create role pod-reader -n demo \
--verb=get,list,watch \
--resource=pods,pods/log
kubectl create rolebinding app-sa-can-read -n demo \
--role=pod-reader \
--serviceaccount=demo:app-sa
# What's in the Role
kubectl get role pod-reader -n demo -o yaml
kubectl describe rolebinding app-sa-can-read -n demo
# Check effective permissions
kubectl auth can-i get pods -n demo --as=system:serviceaccount:demo:app-sa
kubectl auth can-i delete pods -n demo --as=system:serviceaccount:demo:app-sa
# Field structure
kubectl explain role.rules
kubectl explain rolebinding.subjects
kubectl explain rolebinding.roleRef
kubectl create role --resource=deployments fills in the group apps on
its own, while in YAML you write the group yourself.
What you'll need for the tasks
- Lab 1 (
labs/01-watcher-403): the text of a 403 inkubectl logs,kubectl get role -o yaml,kubectl auth can-i ... --as=..., editing a Role withkubectl editorkubectl apply. - Lab 2 (
labs/02-ci-deployer):kubectl create role,kubectl create rolebinding,kubectl create clusterrole,kubectl create clusterrolebinding, checking withauth can-iusing-nand-A. - Lab 3 (
labs/03-module-project, a module mini-project): everything from this lesson plusserviceAccountName,automountServiceAccountTokenfrom the ServiceAccount lesson, andsecurityContextfor therestrictedlevel from the securityContext lesson.
Each lab brings up its own kind cluster on your machine, instructions are
in its README.md.
In short
- Role and RoleBinding work inside a namespace, ClusterRole and ClusterRoleBinding across the whole cluster.
- In a rule,
apiGroups: [""]means the core group; Deployment needsapps. pods/logis a separate subresource: without it,kubectl logsgets a 403 whileget podsworks fine.- A typo in the SA's name or namespace in
subjectscauses no error: the binding quietly grants permissions to a subject that doesn't exist. roleRefis immutable, you change a role by recreating the RoleBinding.kubectl auth can-i ... --as=system:serviceaccount:<ns>:<sa>answers the question about permissions instead of guessing. For cluster-scoped resources, add-A.- Cluster-scoped resources (nodes, PV, CRD) are granted only through ClusterRole + ClusterRoleBinding.
Done reading?
Mark the lesson as read to move your track progress forward.
Lesson marked as done.
Go through it with an AI agent
The agent explains anything unclear in plain words, gives you 3 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.
Tell the agent: use MCP izi, give me lesson k8s-security-03-rbac
Progress saved in this browser
Clear your history or open this on your phone, and the marks will be gone. Leave your email and they'll move to your account. No password needed.
Done. moved to your account. You can see your progress on any device. My progress