OnCallReady

Lesson 17.30 · Kubernetes: Scheduling, Health & Security · 20 min read

RBAC in full: Role, ClusterRole, RoleBinding, ClusterRoleBinding

In plain words

Think of a big office building with key cards. A Role is a list of doors: "meeting rooms on floor 3, read-only". A binding is what actually connects a list to a person: "Jane's card opens the doors on this list". A list that's only valid on floor 3 is a Role; a list that can be used on any floor is a ClusterRole. You can hand someone a building-wide list but only for one floor (a RoleBinding to a ClusterRole), or for every floor (a ClusterRoleBinding).

There are no "forbidden door" lists: permissions only add up. Kubernetes doesn't store people either; it trusts the name printed on the card by whoever issued it (a certificate CN, an OIDC token's claims). The Forbidden error spells out exactly which door was tried: user, verb, resource, API group, namespace.

Where RBAC sits

The problem. Everyone who can reach the apiserver as cluster-admin can delete production. Developers need to read their own namespace, a monitoring agent needs to list pods, a CI job needs to update one Deployment - and nothing more. RBAC (Role-Based Access Control) is how Kubernetes says who may do what.

What you need to know already: the apiserver and etcd (15.5), namespaces (15.26), kubeconfig (15.1), Linux users and groups as the same idea on one machine (4.3), TLS certificates and their CN (9.15, 9.17), admission (17.9).

Two words that sound alike: authentication = proving who you are; authorization = deciding what you may do. Every request to the apiserver goes authentication -> authorization -> admission -> validation -> etcd. Authentication answers who are you (a client certificate, a bearer token - a secret string sent with each request - or an OIDC token: a signed login token from a company login system). RBAC is the authorizer: it answers may this identity perform this verb (action: get, list, create, delete...) on this resource (kind of object: pods, secrets...) in this namespace. It sees exactly four things about a request:

user:      system:serviceaccount:dev:app      groups: [system:serviceaccounts system:serviceaccounts:dev system:authenticated]
verb:      list
resource:  pods        (API group "", subresource none, name none)
namespace: dev

The refusal message is those four things, printed back:

Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:dev:app" cannot list resource "pods" in API group "" in the namespace "dev"

Learn to read it as a spec: who (User), verb (list), resource (pods), API group ("" = core), where (namespace dev, or "at the cluster scope"). Everything you need to write the missing rule is in that sentence.

Subjects: who can be granted anything

A subject is whoever receives permissions:

Your kubernetes-admin kubeconfig user is a certificate with CN=kubernetes-admin and O=kubeadm:cluster-admins, and a ClusterRoleBinding kubeadm:cluster-admins binds that group to cluster-admin:

$ kubectl auth whoami
ATTRIBUTE   VALUE
Username    kubernetes-admin
Groups      [kubeadm:cluster-admins system:authenticated]

kubectl auth whoami asks the apiserver who it thinks you are.

Roles: what may be done

A Role is a list of rules ("these verbs on these resources"). It grants nothing until a binding connects it to a subject (next section).

apiVersion: rbac.authorization.k8s.io/v1
kind: Role                        # namespaced: only meaningful in its own namespace
metadata:
  name: pod-reader
  namespace: dev
rules:
- apiGroups: [""]                 # "" = core (pods, services, configmaps, secrets...)
  resources: ["pods", "pods/log"] # subresources are separate: pods/log, pods/exec, deployments/scale
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["app-config"]   # only this object
  verbs: ["get"]

Generate, do not type (and add $do - the speed-kit variable for --dry-run=client -o yaml, 15.3 - to see the YAML). kubectl create role NAME --verb=... --resource=... builds a Role from flags:

$ kubectl create ns dev
namespace/dev created
$ kubectl create role pod-reader -n dev --verb=get,list,watch --resource=pods,pods/log,deployments
role.rbac.authorization.k8s.io/pod-reader created
$ kubectl describe role pod-reader -n dev
Name:         pod-reader
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources         Non-Resource URLs  Resource Names  Verbs
  ---------         -----------------  --------------  -----
  pods              []                 []              [get list watch]
  pods/log          []                 []              [get list watch]
  deployments.apps  []                 []              [get list watch]

kubectl resolved deployments to the apps group itself - one reason to prefer the generator. deployments.apps in the table is resource.group.

Bindings: who gets which role, and where

A RoleBinding connects a Role (or ClusterRole) to subjects inside one namespace; a ClusterRoleBinding does it cluster-wide. --role/--clusterrole = which role, --user, --group, --serviceaccount=NAMESPACE:NAME = who:

$ kubectl create rolebinding jane-reads-pods -n dev --role=pod-reader --user=jane
$ kubectl create rolebinding app-reads-pods -n dev --role=pod-reader --serviceaccount=dev:app
$ kubectl create clusterrolebinding oncall-view --clusterrole=view --group=oncall

The four combinations, and the one everyone gets wrong:

bindingroleeffect
RoleBinding in ns XRole in ns Xthe rules, in namespace X
RoleBinding in ns XClusterRolethe ClusterRole's rules, only in namespace X
ClusterRoleBindingClusterRolethe rules in every namespace, plus cluster-scoped resources
ClusterRoleBindingRolenot allowed (roleRef.kind must be ClusterRole)

A RoleBinding can reference a ClusterRole, but the grant stays namespaced. This is how you reuse one definition: bind the built-in view ClusterRole with a RoleBinding in dev and the user can view dev - not prod, and not nodes (nodes are cluster- scoped; a namespaced binding can never grant them).

roleRef (the binding's "which role" field) is immutable (cannot be changed) - to point a binding at another role, delete and recreate it:

The RoleBinding "jane-reads-pods" is invalid: roleRef: Invalid value: rbac.RoleRef{APIGroup:"rbac.authorization.k8s.io", Kind:"Role", Name:"pod-admin"}: cannot change roleRef

A binding may reference a role that does not exist yet (it grants nothing until the role appears) - RBAC objects can be applied in any order.

RBAC is purely additive

There are no deny rules. The authorizer walks every ClusterRoleBinding and every RoleBinding in the request's namespace; if any rule in any bound role allows the request, it is allowed. Consequences:

Reading means reading: list and watch on secrets

Say jane asks to see which secrets exist in dev, and someone grants exactly that - list, nothing else:

$ kubectl create secret generic db-creds -n dev --from-literal=password=s3cr3t-p-w0rd
secret/db-creds created
$ kubectl create role secret-lister -n dev --verb=list --resource=secrets
role.rbac.authorization.k8s.io/secret-lister created
$ kubectl create rolebinding jane-lists-secrets -n dev --role=secret-lister --user=jane
rolebinding.rbac.authorization.k8s.io/jane-lists-secrets created

--as=jane = impersonation: send the request as if you were jane (cluster-admin may do this), the quickest way to test someone else's permissions:

$ kubectl get secret db-creds -n dev --as=jane
Error from server (Forbidden): secrets "db-creds" is forbidden: User "jane" cannot get resource "secrets" in API group "" in the namespace "dev"
$ kubectl get secrets -n dev -o yaml --as=jane
apiVersion: v1
items:
- apiVersion: v1
  data:
    password: czNjcjN0LXAtdzByZA==
...

jane has only list on secrets, and get is refused. But a list response contains the full objects, data included: list (and watch) on secrets is read access to every secret in the namespace. The same for configmaps with credentials in them.

The default roles

$ kubectl get clusterroles | grep -v system:
NAME                     CREATED AT
admin                    2026-09-11T08:46:31Z
cluster-admin            2026-09-11T08:46:31Z
edit                     2026-09-11T08:46:31Z
view                     2026-09-11T08:46:31Z
...

admin, edit and view are aggregated ClusterRoles: they carry aggregationRule: {clusterRoleSelectors: [{matchLabels: {rbac.authorization.k8s.io/aggregate-to-view: "true"}}]}, and any ClusterRole you label aggregate-to-view: "true" (the read rules for a CRD - a CustomResourceDefinition, i.e. a new object kind someone added to the cluster - say) is merged into view automatically by a controller.

Escalation prevention

You cannot grant what you do not have. Creating a Role with verbs you lack, or binding a role more powerful than yourself, is refused unless you hold the special verbs escalate (on roles) or bind (on the role being bound). That is why a namespace admin can hand out edit and view in their namespace but cannot mint cluster-admin.

What you can now do

Why it helps

Every "Forbidden" in your career is RBAC, and the error message already contains the missing rule if you can read it: user, verb, resource, API group, namespace. The most common bug, a rule for deployments with apiGroups: [""], grants nothing, and you'll catch it in seconds.

As a platform engineer you'll design the access model: namespace admin for team leads, edit for developers, view for on-call, groups from the company login (OIDC), ServiceAccounts for CI. You'll also answer audit questions like "who can read secrets in prod?", which requires knowing that list on secrets returns their data and that edit includes secrets. On the CKA, creating a Role and RoleBinding for a ServiceAccount is a near-certain task.

Commands in this lesson

kubectl

FAQ

Why doesn't kubectl get users work?

Kubernetes has no User objects. Users and groups are strings that come from the authenticator: a client certificate's CN (user) and O (groups), or an OIDC token's claims. RBAC binds to those strings. ServiceAccounts are the only identities stored as API objects. kubectl auth whoami shows what the API server sees for your current credentials.

Can I bind a ClusterRole with a RoleBinding?

Yes, and it's the standard way to reuse definitions. The grant stays namespaced: binding the built-in view ClusterRole with a RoleBinding in dev lets the subject view dev only, and never cluster-scoped resources like nodes. A ClusterRoleBinding, on the other hand, can only reference a ClusterRole and grants it everywhere.

How do I take away one permission from someone who has edit?

You can't add a "deny". RBAC is purely additive: if any rule in any bound role allows a request, it's allowed. To remove a permission you remove or replace the binding that grants it, for example bind a custom role with fewer verbs instead of edit. Least privilege means few, small bindings.

Is list on secrets safe if I don't give get?

No. A list response contains the full objects, including the data, so list (and watch) on secrets is read access to every secret in the namespace. The same applies to ConfigMaps holding credentials. And anyone who can create pods in a namespace can mount its secrets, so pod creation is secret access too.

Why can't I change the role a binding points to?

roleRef is immutable. The API server rejects the change with "cannot change roleRef". Delete the binding and create a new one. This is deliberate: it keeps a binding's meaning from silently changing under the subjects it grants to.

In an interview Junior

What is the difference between a Role and a ClusterRole, and between a RoleBinding and a ClusterRoleBinding?

RBAC answers "may this identity do this verb on this resource (in this API group) in this namespace?". Roles say what; bindings say who and where.

RBAC is additive - no deny rules; any matching rule allows. The Forbidden message is the spec of the missing rule (user, verb, resource, API group, namespace), the most common bug is the wrong apiGroups (deployments are in apps), and list on secrets is read access to all of them.

Also asked: What are the built-in user-facing ClusterRoles, and what can each do? · Why does RBAC have no deny rules, and what does that mean for removing access? · A ServiceAccount gets "Forbidden: cannot list resource deployments in API group apps". How do you fix it properly?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.