Kubernetes RBAC (Role Based Access Control) determines who can perform which action on which resource in a cluster, through a combination of roles and bindings. On every request to the API, Kubernetes checks whether the requesting person, group or service account holds a matching permission, otherwise the API server rejects the request. One sentence up front, because it clears up the most common misunderstanding: RBAC is not user management. Who you are is settled by authentication, through a certificate, a token or a connected identity provider. RBAC only answers the next question, what you may do as that already established identity. Kubernetes never creates user accounts itself. Without a well thought out RBAC setup, most clusters end up with either far too little or far too much access, and neither works in production.
I run production Kubernetes clusters with roles and permissions built on exactly this model, and I wrote the Kubernetes Practical Guide for Rheinwerk Computing (2024). You can find this article together with all my other Kubernetes topics on my Kubernetes page.
Who can do what: the RBAC model in Kubernetes
Before you create a single role, it helps to look at the three kinds of actors RBAC applies to in the first place. People typically reach the cluster through a kubeconfig, with their name sitting in the common name field of their certificate or coming from an external identity provider. Groups bundle several people together, so you do not have to assign a role to every developer individually but to the whole group at once. Service accounts, finally, are Kubernetes' own identity for pods: every namespace automatically gets a default service account, which applications and processes inside their pods use to talk to the Kubernetes API.
Kubernetes does not manage users and groups itself, that needs an external connection, for example through a certificate, OIDC, or a tool that issues a matching kubeconfig for every person. Service accounts, on the other hand, are proper Kubernetes objects: you create them, bind a role to them, and reference them in the pod.
For an actor to be allowed to do anything at all, you need two parts: a role that describes which actions on which resources are permitted, and a binding that assigns that role to an actor. Without a binding, even the most carefully defined role stays inert, it just sits in the cluster as a definition that nobody benefits from.
Role, ClusterRole and their matching bindings
A Role only applies within a single namespace, a ClusterRole applies cluster-wide or serves as a reusable template. Each of the two has a matching binding that assigns the role to an actor:
| Object |
Scope |
Job |
| Role |
one namespace |
defines permissions for resources within that namespace |
| ClusterRole |
the whole cluster or reusable |
defines permissions for cluster-scoped resources or as a template for several namespaces |
| RoleBinding |
one namespace |
assigns a Role or ClusterRole to an actor within that namespace |
| ClusterRoleBinding |
the whole cluster |
assigns a ClusterRole to an actor cluster-wide |
One detail that gets overlooked in practice: you can assign a ClusterRole through a plain RoleBinding. In that case the permissions only apply within the namespace the RoleBinding lives in, even though the role itself is defined cluster-wide. That saves you from rewriting the same permissions as a separate Role in every namespace.
Inside a role, three fields determine exactly what is allowed: apiGroups groups related resources, the empty group "" stands for core resources such as Pods or Services, apps for example covers Deployments. resources lists the specific object types the role applies to. verbs defines which operations are allowed, among them get, list, watch, create, update, patch, delete and deletecollection. Run kubectl api-resources --sort-by name -o wide to see every object type in your cluster at a glance, including the API group it belongs to.
The following example lets a group of developers read Deployments in the team-shop namespace, but not change them:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-reader
namespace: team-shop
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-shop-read-deployments
namespace: team-shop
subjects:
- kind: Group
name: team-shop-dev
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: deployment-reader
apiGroup: rbac.authorization.k8s.io
The Role defines the permissions, the RoleBinding assigns them to the team-shop-dev group, and both only apply within the team-shop namespace. Following the least privilege principle, you always grant only the verbs and resources a role actually needs, never more.
Service accounts: identity for pods and automation
A namespace's default service account has practically no rights for good reason, it only serves authentication. For every application that actually needs to talk to the Kubernetes API, you therefore create a dedicated service account and bind it, through a Role or ClusterRole, to exactly the permissions that application needs.
The best way to check whether that setup works is before the pod even runs. kubectl auth can-i simulates a request with any actor's permissions:
kubectl auth can-i list deployments \
--as=system:serviceaccount:team-shop:ci-deployer \
--namespace=team-shop
The command answers with yes or no, without you having to roll out a real pod first. The same way, kubectl auth can-i --list --as=system:serviceaccount:team-shop:ci-deployer shows you every permission a service account holds at a glance, which is far faster during troubleshooting than piecing every single permission together by hand.
If a pod needs no access to the Kubernetes API at all, the cleanest option is to turn off the automatic token mount entirely with automountServiceAccountToken: false in the pod spec. When a pod does need the token, the kubelet mounts it as a projected volume with a limited lifetime and automatic renewal, rather than parking it as a Secret in the namespace. How other sensitive values reach a pod is covered in ConfigMap and Secret in Kubernetes.
Limiting a development team to one namespace
A pattern I set up in almost every client cluster: each team gets its own namespace, more on that in Kubernetes Namespaces Done Right, and a RoleBinding that gives the team broad rights inside that namespace and none at all outside it.
Kubernetes already ships with matching ClusterRoles for this: view for read-only access, edit for creating and changing most objects without managing permissions, admin which additionally lets you grant further roles within the namespace, and cluster-admin for unrestricted access to the entire cluster. For a development team, edit is usually enough, bound through a namespace-scoped RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-shop-edit
namespace: team-shop
subjects:
- kind: Group
name: team-shop-dev
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: edit
apiGroup: rbac.authorization.k8s.io
That lets the team do almost anything inside its own namespace, while it sees neither other teams' objects nor cluster-scoped resources such as nodes or namespaces themselves. That is the difference between a team that can work productively and a team that accidentally changes someone else's namespace.
Auditing permissions and avoiding common RBAC mistakes
The most common mistake I see in clusters that have grown over time is the pragmatic shortcut under time pressure: cluster-admin for everyone, because otherwise a permission always seems to be missing. That works right up until a single compromised pod or a misconfigured kubeconfig is enough to take over the entire cluster. Wildcards in resources or verbs are just as risky: a star in the verbs is practically equivalent to cluster-admin for the affected resources, just less visible in the manifest.
More RBAC best practices from my own work: bind roles to groups rather than individual people, so a team change means updating group membership instead of hunting down ten bindings. Audit permissions regularly, not only when you create them: kubectl auth can-i --list --as=<subject> shows you, for any actor, what they can actually do right now, and reliably surfaces forgotten bindings. And checking kubectl get rolebindings,clusterrolebindings -A is a fixed part of every security review I run, because an astonishing amount accumulates there over months that nobody needs anymore.
RBAC for GitOps, CI and operators
Automated actors need permissions thought through just as carefully as people, often more urgently, because they do not pause to reconsider when a permission is too broad. A CI pipeline that rolls out Deployments into a namespace gets its own service account with a role covering exactly the resources and verbs it needs for that, but for example no access to secrets in other namespaces.
A GitOps controller that keeps an eye on many namespaces at once, on the other hand, almost inevitably needs a ClusterRole, because it has to read and reconcile objects cluster-wide. It still pays off to keep write permissions as narrow as possible, scoped to the projects or applications actually managed, instead of using one single, very powerful ClusterRole for everything. Operators that bring their own custom resources also need a ClusterRole, because they watch their CRDs cluster-wide, but the actual instances of those resources can still be further constrained through namespaces and additional roles. Starting with the smallest roles possible from day one means you never trade the whole cluster for one compromised automation account later.
Frequently asked questions
What is the difference between Role and ClusterRole?
A Role only applies within the namespace it lives in, a ClusterRole either applies cluster-wide or serves as a reusable template that you can still make effective in a single namespace only, through a RoleBinding. Plain namespace permissions usually need only a Role, cluster-scoped resources such as nodes or reusable role templates need a ClusterRole.
How do I give a user read-only access?
Bind the built-in view ClusterRole to the user or their group through a RoleBinding in the target namespace, then they can see everything there but change nothing. For a single, very narrow read permission, say only on Deployments, define your own Role with the verbs get, list and watch instead.
How do I see which permissions a service account has?
The fastest way is kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<name>. The command lists every resource and verb the service account can actually use, without you first having to piece together every role and binding by hand.
Is RBAC enough to secure a Kubernetes cluster?
No, RBAC only controls who can perform which action on which Kubernetes resource, not what happens inside a container. A pod with overly broad container permissions can still endanger the node despite clean RBAC, for that you additionally need Pod Security Standards and ongoing monitoring of cluster behavior.
Where to go next
The next building block for a secure cluster is the security context of individual pods, more on that in Kubernetes Pod Security Standards. My suggestion for today: run kubectl auth can-i --list across your most important namespaces and check how many bindings there are actually still needed. The full reference for all RBAC objects is in the Kubernetes documentation on RBAC, and details on authenticating users are in the documentation on authentication.
In full detail with all examples, this is covered in chapter 7 of my Kubernetes Practical Guide (Rheinwerk Computing).