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
+---------------------------+ +---------------------------+ +---------------------------+| 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:
+------------------------+ +------------------------+ +------------------------+| payments-team | | platform-team | | ci-bot || | | | | || manage pods/deploys | | read across all | | update deployments || in their namespace | | namespaces to debug | | (image tags) only || only | | | | |+------------------------+ +------------------------+ +------------------------+# ── SCENARIO 1: Namespace-scoped developer access ──apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: developer namespace: payments-prodrules: - 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/v1kind: RoleBindingmetadata: name: payments-developer-binding namespace: payments-prodsubjects: - kind: User name: rahul@devops.in # A specific engineer apiGroup: rbac.authorization.k8s.ioroleRef: kind: Role name: developer apiGroup: rbac.authorization.k8s.io# ── SCENARIO 2: Cluster-wide read-only for platform team ──apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRolemetadata: name: platform-readonlyrules: - apiGroups: ["*"] resources: ["*"] verbs: ["get", "list", "watch"] # Read everything, touch nothing---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: platform-team-bindingsubjects: - kind: Group name: platform-engineers # Entire group — not individual users apiGroup: rbac.authorization.k8s.ioroleRef: kind: ClusterRole name: platform-readonly apiGroup: rbac.authorization.k8s.io# ── SCENARIO 3: CI bot — only update deployments ──apiVersion: rbac.authorization.k8s.io/v1kind: Rolemetadata: name: ci-deployer namespace: payments-prodrules: - apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "patch", "update"] # Just enough to update image tags---apiVersion: rbac.authorization.k8s.io/v1kind: RoleBindingmetadata: name: ci-bot-binding namespace: payments-prodsubjects: - kind: ServiceAccount name: github-actions-bot namespace: payments-prodroleRef: kind: Role name: ci-deployer apiGroup: rbac.authorization.k8s.ioThe 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
+------------------------------------------+| 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
# 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 namespacekubectl auth can-i --list -n payments-prod # List all RoleBindings in a namespace to audit who has whatkubectl get rolebindings -n payments-prod -o wide # Find all ClusterRoleBindings that grant cluster-adminkubectl 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 |
SecurityNever grant
*verbs onsecretsto 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.
TipRun
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 a403 Forbiddenerror in a CI pipeline without reading through 10 YAML files.
Remember
RoleBindingcan reference aClusterRole— and this is intentional. Define aClusterRolewith standard developer permissions once, then bind it per-namespace with individualRoleBindings. This avoids duplicating Role YAML across every team namespace.
Common MistakeGranting
cluster-adminto 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 minimalci-deployerRole 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.