If you’ve ever managed a Kubernetes cluster with more than one person on the team, you’ve probably run into the question: “Why does this developer have permission to delete production Pods?” That question is exactly what Role-Based Access Control (RBAC) exists to answer — and to prevent. In this guide, I’ll break down how RBAC works in Kubernetes from the ground up, and walk through practical examples you can apply immediately.
Why RBAC Matters
Kubernetes clusters often host multiple teams, multiple environments, and multiple layers of automation (CI/CD pipelines, controllers, monitoring agents) all talking to the same API server. Without access control, any authenticated user or service account could do anything — read secrets, delete deployments, modify RBAC itself. RBAC solves this by letting you define who can do what, on which resources, in which namespaces.
Kubernetes Architecture Context
Every request to Kubernetes — whether from kubectl, a controller, or a CI pipeline — goes through three stages at the API server:
- Authentication: Who are you? (certificates, tokens, OIDC, service accounts)
- Authorization: Are you allowed to do this? (this is where RBAC lives)
- Admission Control: Should this specific request be allowed/modified? (webhooks, policies)
RBAC is the authorization layer. It doesn’t care who you are beyond your identity (user or service account) and group memberships — it just checks whether a Role permits the verb and resource you’re requesting.
The Four RBAC Objects
RBAC in Kubernetes is built from four API objects:
- Role: A set of permissions (verbs on resources) scoped to a single namespace.
- ClusterRole: The same thing, but scoped cluster-wide (or reusable across namespaces).
- RoleBinding: Grants a Role to a user, group, or service account within a namespace.
- ClusterRoleBinding: Grants a ClusterRole cluster-wide.
The key mental model: Roles/ClusterRoles define permissions; Bindings assign those permissions to someone. A Role by itself does nothing until it’s bound.
Step 1: Create a Namespace-Scoped Role
Let’s say a developer needs to view and manage Pods in the staging namespace, but nothing else.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: staging
name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Apply it:
kubectl apply -f pod-manager-role.yaml
Step 2: Bind the Role to a User
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-manager-binding
namespace: staging
subjects:
- kind: User
name: jane.doe
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-manager
apiGroup: rbac.authorization.k8s.io
kubectl apply -f pod-manager-binding.yaml
Now Jane can manage Pods only inside staging — nothing else, and nowhere else.
Step 3: Cluster-Wide Permissions with ClusterRole
Suppose you have a monitoring service account that needs to read node metrics across the whole cluster.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/metrics"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-reader-binding
subjects:
- kind: ServiceAccount
name: monitoring-agent
namespace: monitoring
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
Understanding Verbs and Resources
Common verbs: get, list, watch, create, update, patch, delete, deletecollection. Resources can be scoped further using resourceNames to restrict access to specific named objects:
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get", "update"]
This grants access only to the app-config ConfigMap — nothing else in that resource type.
Aggregated ClusterRoles
For larger clusters, Kubernetes supports aggregated ClusterRoles, which combine multiple ClusterRoles using label selectors. This is how the built-in admin, edit, and view roles work internally, and it’s a clean pattern for building modular permission sets:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-aggregate
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-endpoints
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["endpoints", "services", "pods"]
verbs: ["get", "list", "watch"]
Checking Permissions
Before you go debugging why something isn’t working, use kubectl auth can-i:
kubectl auth can-i delete pods --namespace staging --as jane.doe
# yes
kubectl auth can-i create deployments --namespace production --as jane.doe
# no
This is invaluable for both testing and troubleshooting RBAC issues without waiting for a user to hit an error.
RBAC for Service Accounts (CI/CD and Automation)
Every Pod runs with a ServiceAccount, and by default it’s the default service account in its namespace, which typically has minimal permissions. For a CI/CD pipeline deploying to a namespace:
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-deployer
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deployer-binding
namespace: production
subjects:
- kind: ServiceAccount
name: ci-deployer
namespace: production
roleRef:
kind: Role
name: deployer
apiGroup: rbac.authorization.k8s.io
Your pipeline (GitHub Actions, GitLab CI, Argo CD) then authenticates using this ServiceAccount’s token, scoped to exactly the deployments it needs to touch.
Security Best Practices
- Principle of least privilege: Never grant
cluster-adminunless absolutely necessary. Audit who has it withkubectl get clusterrolebindings -o json | jq. - Avoid wildcard rules (
resources: ["*"],verbs: ["*"]) in production; they’re a common source of privilege escalation. - Use groups, not individual users, for bindings when integrating with an identity provider (OIDC) — it scales much better as teams grow.
- Separate namespaces per environment/team and scope Roles accordingly, rather than relying on a handful of broad ClusterRoles.
- Rotate and audit service account tokens, especially long-lived ones; prefer projected, time-bound tokens (
BoundServiceAccountToken) which are the default in modern Kubernetes. - Watch for privilege escalation via RBAC itself — a user with
createonrolebindingsandbindverb capability could grant themselves broader access; Kubernetes has built-in escalation checks, but review carefully.
Common Mistakes
- Binding a ClusterRole via a RoleBinding to try to limit it to a namespace — this actually works and is a valid pattern, but it’s easy to confuse with a ClusterRoleBinding, which grants access everywhere.
- Forgetting
pods/logandpods/execare separate subresources frompods— grantinggetonpodsdoesn’t let someonekubectl execinto them. - Applying RBAC changes without testing with
kubectl auth can-i --as, leading to either broken pipelines or unnoticed over-permissioning.
Troubleshooting RBAC Issues
When a request is denied, the API server returns a clear Forbidden error naming the exact verb, resource, and namespace it evaluated:
Error from server (Forbidden): pods is forbidden: User "jane.doe" cannot list resource "pods" in API group "" in the namespace "production"
Read this message literally — it tells you exactly which Role or ClusterRoleBinding you’re missing.
Summary
RBAC is the backbone of multi-tenant, secure Kubernetes operations. By combining Roles and ClusterRoles (what’s allowed) with RoleBindings and ClusterRoleBindings (who gets it), you can build precise, auditable access control for humans, CI pipelines, and controllers alike. Start from least privilege, use kubectl auth can-i liberally, and treat every wildcard rule as a red flag worth double-checking.