Skip to main content

RBAC

Role-Based Access Control — Kubernetes's built-in authorization system that controls who (users, groups, or ServiceAccounts) can perform what actions (get, list, create, delete) on which resources (pods, secrets, deployments) within the cluster. RBAC is enforced through four objects: Role, ClusterRole, RoleBinding, and ClusterRoleBinding.

RBAC — Controlling Who Can Do What in Kubernetes

The Four RBAC Objects — Explained Simply

◈ DIAGRAM
+---------------------------+ +---------------------------+ +---------------------------+
| WHO | | WHAT (permissions) | | SCOPE |
| | | | | |
| ServiceAccount | | Role | | One namespace only |
| User | --> | ClusterRole | --> | Entire cluster |
| Group | | | | |
+---------------------------+ +---------------------------+ +---------------------------+
Binding objects connect WHO to WHAT:
+------------------------------------------+
| RoleBinding -> Role or ClusterRole in ONE namespace |
| ClusterRoleBinding -> ClusterRole across ALL namespaces |
+------------------------------------------+

A Concrete Team Setup — PhonePe Multi-Team Cluster

PhonePe has three teams sharing one cluster, each with different access needs:

◈ DIAGRAM
+------------------------+ +------------------------+ +------------------------+
| payments-team | | platform-team | | ci-bot |
| | | | | |
| manage pods/deploys | | read across all | | update deployments |
| in their namespace | | namespaces to debug | | (image tags) only |
| only | | | | |
+------------------------+ +------------------------+ +------------------------+
YAML
# ── SCENARIO 1: Namespace-scoped developer access ──
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: payments-prod
rules:
- apiGroups: ["apps", ""]
resources: ["deployments", "pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"] # Can READ secrets, not create or delete them
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-developer-binding
namespace: payments-prod
subjects:
- kind: User
name: rahul@devops.in # A specific engineer
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
YAML
# ── SCENARIO 2: Cluster-wide read-only for platform team ──
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: platform-readonly
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["get", "list", "watch"] # Read everything, touch nothing
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: platform-team-binding
subjects:
- kind: Group
name: platform-engineers # Entire group — not individual users
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: platform-readonly
apiGroup: rbac.authorization.k8s.io
YAML
# ── SCENARIO 3: CI bot — only update deployments ──
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: ci-deployer
namespace: payments-prod
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "patch", "update"] # Just enough to update image tags
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-bot-binding
namespace: payments-prod
subjects:
- kind: ServiceAccount
name: github-actions-bot
namespace: payments-prod
roleRef:
kind: Role
name: ci-deployer
apiGroup: rbac.authorization.k8s.io

The Verbs Cheatsheet

Verb What it allows
get Read a specific named resource
list List all resources of a type
watch Stream changes to resources in real-time
create Create new resources
update Replace an entire resource object
patch Modify specific fields of a resource
delete Delete a single resource
deletecollection Delete all resources matching a selector
* All verbs — full admin access, use sparingly

How RBAC Request Evaluation Works

Bash
+------------------------------------------+
| kubectl request arrives at API server | <- e.g. rahul tries to delete a Secret
+------------------------------------------+
|
v
+------------------------------------------+
| Authentication: who is making this call? | <- Certificate, token, or kubeconfig
+------------------------------------------+
|
v
+------------------------------------------+
| Authorization: is this action allowed? | <- RBAC checks Role/ClusterRole rules
+------------------------------------------+
| |
v v
+------------+ +------------+
| ALLOWED | | FORBIDDEN |
| 200 OK | | 403 Error |
+------------+ +------------+

Testing RBAC Without Guessing

Bash
# Can the GitHub Actions SA update deployments in payments-prod?
kubectl auth can-i update deployments \
--as=system:serviceaccount:payments-prod:github-actions-bot \
-n payments-prod
# Output: yes
# Can it delete pods? (should be no)
kubectl auth can-i delete pods \
--as=system:serviceaccount:payments-prod:github-actions-bot \
-n payments-prod
# Output: no
# Full dump of everything YOUR current user can do in a namespace
kubectl auth can-i --list -n payments-prod
# List all RoleBindings in a namespace to audit who has what
kubectl get rolebindings -n payments-prod -o wide
# Find all ClusterRoleBindings that grant cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects'

Common RBAC Mistakes

Mistake Consequence Fix
Using ClusterRoleBinding for team access Team can read secrets in all namespaces Use RoleBinding scoped to their namespace only
Granting * verbs on * resources Full cluster admin for app pods List exact verbs and resources — least privilege
Forgetting the apiGroups field Rules silently do not apply Use "" for core resources, "apps" for Deployments
Binding cluster-admin to CI service accounts Compromised pipeline = full cluster takeover Create a minimal ci-deployer Role with only patch/update on deployments
Not auditing RBAC after team changes Departed engineers retain access Audit bindings quarterly with kubectl get rolebindings -A
Security

Never grant * verbs on secrets to non-admin users or service accounts. Secrets contain database passwords, API keys, and TLS certificates. On a Zerodha-scale trading platform, a compromised pod with secret read access can exfiltrate every downstream service credential in minutes.

Tip

Run kubectl auth can-i --list -n <namespace> to get a full dump of what your current user is allowed to do. This is the fastest way to debug a 403 Forbidden error in a CI pipeline without reading through 10 YAML files.

Remember

RoleBinding can reference a ClusterRole — and this is intentional. Define a ClusterRole with standard developer permissions once, then bind it per-namespace with individual RoleBindings. This avoids duplicating Role YAML across every team namespace.

Common Mistake

Granting cluster-admin to service accounts used by CI tools like GitHub Actions or ArgoCD. If the pipeline is compromised, the attacker has unrestricted access to the entire cluster. Always create a minimal ci-deployer Role with exactly the verbs the pipeline needs — nothing more.

Frequently Asked Questions

What's the actual difference between a Role and a ClusterRole in RBAC?

A Role is namespace-scoped — its permissions only apply within the namespace it's defined in — while a ClusterRole is cluster-scoped and can grant permissions across all namespaces, or govern cluster-scoped resources like nodes and PersistentVolumes that don't belong to any namespace. Confusingly, a ClusterRole can still be bound to a single namespace via a RoleBinding (rather than a ClusterRoleBinding), which is a common pattern for reusing one set of permission verbs across many namespaces without duplicating Role definitions.

What's a common RBAC misconfiguration that grants far more access than intended?

Binding `cluster-admin` — Kubernetes's built-in superuser ClusterRole — to a ServiceAccount for convenience during setup, and then never revisiting it. That ServiceAccount's token, if it leaks from a compromised pod, grants full control of the entire cluster. The safer pattern is to define narrowly scoped Roles listing only the verbs and resources a workload actually needs, and audit bindings periodically with `kubectl auth can-i --list` for a given ServiceAccount.