DEV Community

Cover image for Kubernetes RBAC Explained by Breaking It: Practice Roles and Bindings in Your Browser
DevOps Daily
DevOps Daily

Posted on

Kubernetes RBAC Explained by Breaking It: Practice Roles and Bindings in Your Browser

Your CI service account needs to read pods. Someone writes a ClusterRole called pod-reader, binds it with a RoleBinding in dev, and it works. A month later the same job runs against prod and gets Forbidden. "But it's a ClusterRole," says everyone in the channel. The fix someone reaches for is a ClusterRoleBinding, and now the CI account can read pods in kube-system too.

Kubernetes RBAC Simulator on DevOps Daily

Both surprises come from the same rule, and it is the one to learn first if you want Kubernetes RBAC explained in a way that sticks: the role says what, the binding says where. A ClusterRole is only cluster-wide if a ClusterRoleBinding makes it so.

This post walks through RBAC by breaking it on purpose in the Kubernetes RBAC Simulator. Disclosure: I help build it; it is free, runs in the browser, no signup, and needs no cluster.

Four objects, two questions

RBAC has four object kinds:

  • Role: a list of rules, living in one namespace.
  • ClusterRole: a list of rules with no namespace. Reusable anywhere, and the only way to describe cluster-scoped resources such as nodes.
  • RoleBinding: grants a Role or a ClusterRole to subjects (users, groups, service accounts) inside the binding's own namespace.
  • ClusterRoleBinding: grants a ClusterRole in every namespace and on cluster-scoped resources.

A rule is apiGroups + resources + verbs, optionally narrowed by resourceNames. And the line from the official docs that answers half of all RBAC questions: "Permissions are purely additive (there are no "deny" rules)."

So every "can X do Y?" question comes down to: which bindings name X, and does any of them grant Y in this place?

How the simulator works

The simulator has seven fixed scenarios. Each one shows its Roles and bindings as YAML in one pane. In the other you build a question: pick a subject, one of eight verbs (get to deletecollection), one of seven resources (pods to namespaces), a namespace (dev, staging, prod, kube-system, or cluster scope, which for a namespaced resource like pods means every namespace) and optionally a named object. The answer is Allowed or Denied, with a sentence naming the binding and role that granted it. Every binding that names the subject but did not grant the request is listed with the reason. Under that you get the full list of verbs the subject has on that resource, and a kubectl auth can-i command for the same question.

Break it in seven moves

1. The ordinary case. A Role and a RoleBinding, both in dev. list pods in dev is allowed, "granted by RoleBinding/ci-reads-pods through Role/pod-reader, which applies to namespace dev." This is the model people then over-apply.

2. The one that surprises everyone. Same service account, but the role is now a ClusterRole bound by a RoleBinding in dev. The scenario asks about prod, and the answer is Denied: the binding "binds ClusterRole/pod-reader, but a RoleBinding confines it to dev. The permissions are cluster-wide in definition and namespaced in effect." Switch the namespace to dev and it is allowed. The official sample manifest says it in a comment: "The namespace of the RoleBinding determines where the permissions are granted."

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-reads-pods-in-dev
  namespace: dev            # this line decides where
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-reader
subjects:
  - kind: ServiceAccount
    name: ci
    namespace: dev
Enter fullscreen mode Exit fullscreen mode

3. Nodes are not in a namespace. A ClusterRole that allows get and list on nodes, bound with a RoleBinding. Denied. A namespaced binding can never grant a cluster-scoped resource, however broad the role is.

4. One secret, not the list. A Role with resourceNames: ["db-password"] and only the verb get. get secrets/db-password is allowed. Switch the verb to list and it is denied, because the Role grants only get. Clear the named-object field too and you have the question kubectl get secrets asks: still denied, because a rule with resourceNames does not cover a plain list. The docs add one exception: if a rule grants list or watch with resourceNames, a request with a matching metadata.name field selector is authorized.

5. The silent typo. The binding's roleRef says pod-readr. Denied, with the reason: the role does not exist, and "a dangling roleRef grants nothing and raises no error." Kubernetes skips the lookup for callers that hold the bind verb or are in system:masters, so a cluster admin can apply this binding without an error and no event fires. Anyone else gets rolebindings.rbac.authorization.k8s.io "pod-readr" not found when they create it. The silence ends at request time: on a real API server, the service account's Forbidden error ends with RBAC: role.rbac.authorization.k8s.io "pod-readr" not found, and kubectl auth can-i prints the same reason after its no. Read the whole error. And roleRef is immutable after creation, so the fix is delete and recreate.

6. There is no deny. Two bindings, one to pod-reader and one to an empty Role called read-only-audit. The empty Role takes nothing away. If you are looking for the rule that revoked access, there is not one; look for the grant that is missing.

7. Genuinely cluster-wide. The same ClusterRole, now bound with a ClusterRoleBinding. list pods in kube-system is allowed. This is the one to be careful with, and the one from the opening story.

The permission you did not mean to grant

The simulator answers "who can do what, and why". It does not model escalation paths, and those are where a harmless-looking grant turns into much more. The Kubernetes RBAC good practices page lists them. Three to check in every review:

  • list or watch on Secrets. The docs say it plainly: these "effectively allow for users to reveal the Secret contents". A list response includes the data. A role that "only lists" secrets can read them all.
  • Creating Pods, or anything that creates Pods. A Pod can mount Secrets, ConfigMaps and volumes from its namespace and run as any service account there. In the docs' words, permission to create workloads "implicitly grants access to many other resources in that namespace", and that includes create on Deployments or Jobs.
  • The escalate, bind and impersonate verbs. The first two bypass the checks that stop you granting more than you hold. impersonate lets you act as someone else. None of them appear in the simulator's verb list, so check them by reading your roles, including any wildcard * in verbs or resources.

The built-in edit ClusterRole already holds two of these risks: read on Secrets, and impersonate on serviceaccounts. The docs say it "can be used to gain the API access levels of any ServiceAccount in the namespace." Bind it with a ClusterRoleBinding and that is every namespace.

Ask a real cluster

Every answer in the simulator comes with a can-i command to try. These two are the ones to remember, adapted from the examples in kubectl auth can-i --help:

# one question, asked as the service account
kubectl auth can-i list pods -n prod --as=system:serviceaccount:dev:ci

# everything that account can do in a namespace
kubectl auth can-i --list -n dev --as=system:serviceaccount:dev:ci
Enter fullscreen mode Exit fullscreen mode

--as is impersonation, so you need permission to impersonate, which usually means a cluster admin. Also note the -n: without it, can-i answers for your current namespace, which may be a different question from the one you meant. For a single check across every namespace, use --all-namespaces; --list refuses that flag.

One trap for cluster-scoped resources: -n still goes into the question. I rebuilt move 3 on a real kube-apiserver v1.37.1. kubectl auth can-i list nodes -n dev --as=system:serviceaccount:dev:ci printed Warning: resource 'nodes' is not namespace scoped, then yes. As the same account, kubectl get nodes got Forbidden "at the cluster scope". With --all-namespaces instead of -n dev, can-i says no. For nodes and other cluster-scoped resources, ask with --all-namespaces. On that server, the real requests in all seven moves got the same verdicts as the simulator gives.

Where to go next

Run the seven scenarios in order, and in each one change one field at a time: namespace, verb, then resource. Watching a verdict flip is faster than reading about it. The simulator is one of 50+ free DevOps games and simulators. The primary sources are the Kubernetes RBAC authorization reference and the good practices page linked above.

What is the most surprising permission you have found granted in a cluster? Tell us in the comments.

Top comments (0)