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 | | | | |+------------------------+ +------------------------+ +------------------------+1# āā SCENARIO 1: Namespace-scoped developer access āā2apiVersion: rbac.authorization.k8s.io/v13kind: Role4metadata:5 name: developer6 namespace: payments-prod7rules:8 - apiGroups: ["apps", ""]9 resources: ["deployments", "pods", "services", "configmaps"]10 verbs: ["get", "list", "watch", "create", "update", "patch"]11 - apiGroups: [""]12 resources: ["secrets"]13 verbs: ["get", "list"] # Can READ secrets, not create or delete them1415apiVersion: rbac.authorization.k8s.io/v116kind: RoleBinding17metadata:18 name: payments-developer-binding19 namespace: payments-prod20subjects:21 - kind: User22 name: rahul@devops.in # A specific engineer23 apiGroup: rbac.authorization.k8s.io24roleRef:25 kind: Role26 name: developer27 apiGroup: rbac.authorization.k8s.io1# āā SCENARIO 2: Cluster-wide read-only for platform team āā2apiVersion: rbac.authorization.k8s.io/v13kind: ClusterRole4metadata:5 name: platform-readonly6rules:7 - apiGroups: ["*"]8 resources: ["*"]9 verbs: ["get", "list", "watch"] # Read everything, touch nothing1011apiVersion: rbac.authorization.k8s.io/v112kind: ClusterRoleBinding13metadata:14 name: platform-team-binding15subjects:16 - kind: Group17 name: platform-engineers # Entire group ā not individual users18 apiGroup: rbac.authorization.k8s.io19roleRef:20 kind: ClusterRole21 name: platform-readonly22 apiGroup: rbac.authorization.k8s.io1# āā SCENARIO 3: CI bot ā only update deployments āā2apiVersion: rbac.authorization.k8s.io/v13kind: Role4metadata:5 name: ci-deployer6 namespace: payments-prod7rules:8 - apiGroups: ["apps"]9 resources: ["deployments"]10 verbs: ["get", "patch", "update"] # Just enough to update image tags1112apiVersion: rbac.authorization.k8s.io/v113kind: RoleBinding14metadata:15 name: ci-bot-binding16 namespace: payments-prod17subjects:18 - kind: ServiceAccount19 name: github-actions-bot20 namespace: payments-prod21roleRef:22 kind: Role23 name: ci-deployer24 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
1# Can the GitHub Actions SA update deployments in payments-prod?2kubectl auth can-i update deployments \3 --as=system:serviceaccount:payments-prod:github-actions-bot \4 -n payments-prod5# Output: yes6 7# Can it delete pods? (should be no)8kubectl auth can-i delete pods \9 --as=system:serviceaccount:payments-prod:github-actions-bot \10 -n payments-prod11# Output: no12 13# Full dump of everything YOUR current user can do in a namespace14kubectl auth can-i --list -n payments-prod15 16# List all RoleBindings in a namespace to audit who has what17kubectl get rolebindings -n payments-prod -o wide18 19# Find all ClusterRoleBindings that grant cluster-admin20kubectl get clusterrolebindings -o json | \21 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 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.
š” Tip: Runkubectl 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 Mistake: Grantingcluster-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.