Understanding Kubernetes: RBAC Without Losing Your Mind
In the previous articles we looked at different parts of Kubernetes networking: how Pods communicate, how Services provide stable access, and how Network Policies can restrict traffic.
Today we want to answer a slightly different question: Just because something can access a resource, should it? Let’s say a frontend application might need to talk to a backend service. But should it also be able to read Secrets? Or should every developer in the team be able to delete Deployments in production?
This is where RBAC comes in - Role-Based Access Control. It allows us to define who can do what and where. And once you understand the basic building blocks, it is actually not as scary as it might look at first. 😉
The basic idea behind RBAC
RBAC answers three simple questions:
- Who is trying to do something?
- What do they want to do?
- Where should this be allowed?
For example:
“Allow the frontend application (who) to read ConfigMaps (what) in the production namespace (where).”
RBAC does not directly assign permissions to users or applications. Instead, we create reusable permission definitions and then connect them to identities.
To work effectively with RBAC, there are four main objects we need to understand:
- Role
- RoleBinding
- ClusterRole
- ClusterRoleBinding
Let’s start with the smallest scope.
Roles: Permissions inside a namespace
A Role defines permissions within a single namespace.
For example, we want to allow someone to read Pods in the development namespace. A Role could look like this:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: development
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
The important part here is the rule
resources:
- pods
verbs:
- get
- list
saying “Someone with this Role can get and list Pods.” But the Role itself does not define who gets this permission. For that, we need a Binding.
RoleBindings connect permissions to identities
A RoleBinding assigns a Role to someone, for example:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: development
subjects:
- kind: User
name: alice
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
allows Alice to read Pods in the development namespace. But she cannot delete Pods, access Secrets or read Pods in other namespaces, because we did not grant these permissions. This is also the basic RBAC principle: Only grant the permissions that are actually needed.
ClusterRoles: permissions beyond namespaces
Sometimes permissions should not be limited to a single namespace, e.g. for viewing Nodes, managing namespaces or accessing cluster-wide resources. For this, Kubernetes provides ClusterRoles.
A ClusterRole looks very similar to a Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
But the difference is the scope: While a Role lives inside a namespace, a ClusterRole applies cluster-wide (as the name already suggests). To assign a ClusterRole, we then use a ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-reader-binding
subjects:
- kind: User
name: alice
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
This now allows Alice to read Nodes across the whole cluster.
Users vs ServiceAccounts
One thing that often causes confusion: Who exactly is the “user” in Kubernetes?
There are two important types of identities: Users are usually humans, e.g. developers, administrators, or operators, like Alice in our example from above. ServiceAccounts are identities for workloads running inside Kubernetes.
Imagine an application that needs to read ConfigMaps. The application itself is not a person, so we create a ServiceAccount:
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: production
Then we can bind permissions to this ServiceAccount. This is how many controllers and Kubernetes operators work internally.
Verbs, resources and API groups
When writing RBAC rules, you will often find these three fields:
- Verbs describe which actions are allowed, e.g. get, list, create, update, or delete
- Resources are e.g. Pods, Deployments, Services, or Secrets, so Kubernetes objects we want to access.
- API Groups describe where the resource belongs to. This is where it gets a bit trickier: we need to reference the correct API group.
Pods, for example, are part of the core API, whereas Deployments are part of the apps API group - and hence both get referenced differently:
# Pods:
apiGroups:
- ""
# Deployments:
apiGroups:
- apps
This is why RBAC rules sometimes look a bit confusing in the beginning.
If you are unsure which API group or resource name to use, kubectl api-resources is your friend. It will show you the available resources and their API groups.
Be aware: RBAC is not automatically secure
Kubernetes comes with some predefined ClusterRoles like cluster-admin. Be careful when using these, because they often grant far more permissions than a workload actually needs.
Demo time
Let’s try this out in a small example. First, we create a namespace via
kubectl create namespace demo
and then a ServiceAccount:
kubectl create serviceaccount pod-reader \
--namespace demo
Next, we create a Role that allows reading Pods
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: demo
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
and apply it via
kubectl apply -f role.yaml
Now we connect it to the ServiceAccount:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: demo
subjects:
- kind: ServiceAccount
name: pod-reader
namespace: demo
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
We apply again
kubectl apply -f rolebinding.yaml
so we can test our permissions. We don’t need to create a Pod for this demo. Instead, kubectl auth can-i allows us to test permissions by impersonating this ServiceAccount.
kubectl auth can-i get pods \
--namespace demo \
--as=system:serviceaccount:demo:pod-reader
The answer should be yes. Worked? Great, we can read Pods. Let’s try now if we can delete pods via
kubectl auth can-i delete pods \
--namespace demo \
--as=system:serviceaccount:demo:pod-reader
Got no? Exactly what we wanted. :)
Summing up
RBAC is Kubernetes’ way of controlling access - and one of the foundations of securing clusters. The core idea is simple: Roles define permissions, Bindings assign permissions, and ServiceAccounts give applications an identity.
Together with Network Policies, Secrets management, and Pod Security, it helps us move from a very open default setup to a more controlled and secure environment. If you want to dig deeper into Secrets management, have a look at my article about ConfigMaps & Secrets I wrote a while back. Next up in this series: Pod Security. Stay tuned :)
header image created by buddy ChatGPT
