Introduction
Kubernetes RBAC is the last line of defense between an attacker who compromises a pod and full cluster control. A single misconfigured RoleBinding — granting pods/exec to a compromised service account — can escalate a container escape into complete cluster takeover.
Yet RBAC is also the most misunderstood Kubernetes security primitive. Teams default to cluster-admin bindings, grant wildcard * verbs, and never audit who accessed what. This guide fixes that with production-tested YAML patterns and real attack scenarios.
Role vs ClusterRole: When Namespace Matters
The fundamental RBAC decision: does this permission apply to one namespace or the entire cluster?
Role: Namespace-scoped. Use for application service accounts that only need access to their own namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: payment-service
name: payment-deployer
rules:
- apiGroups: ["apps", ""]
resources: ["deployments", "pods", "configmaps"]
verbs: ["get", "list", "watch", "create", "update"]
ClusterRole: Cluster-scoped. Use for cluster-wide resources (nodes, namespaces, persistent volumes) or to bind the same permissions across multiple namespaces.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes", "nodes/metrics", "nodes/proxy"]
verbs: ["get", "list", "watch"]
Rule of thumb: Start with Role + RoleBinding. Upgrade to ClusterRole only when you have proven the need for cluster-wide access. ClusterRole + RoleBinding (which scopes the ClusterRole to one namespace) is a legitimate pattern — the ClusterRole defines the permissions template, but binding it in payment-service only grants those permissions in that namespace.
RoleBinding and ClusterRoleBinding: The Grant
RoleBinding grants a Role to a subject (user, group, or service account) within a namespace. ClusterRoleBinding grants a ClusterRole cluster-wide.
# Namespaced grant
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-deployer-binding
namespace: payment-service
subjects:
- kind: ServiceAccount
name: payment-deployer
namespace: payment-service
roleRef:
kind: Role
name: payment-deployer
apiGroup: rbac.authorization.k8s.io
# Cluster-wide grant — USE SPARINGLY
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-reader-binding
subjects:
- kind: Group
name: sre-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.io
Never bind to system:masters group. This is equivalent to root. If you see a ClusterRoleBinding with subjects[].name: system:masters, remove it immediately.
Service Account Hardening: The Pod Attack Surface
When a pod runs, it uses a service account. The default service account in each namespace exists even if you never created one — and if RBAC is not configured, it may have unintended permissions.
Step 1: Disable automounting the default token.
apiVersion: v1
kind: ServiceAccount
metadata:
name: default
namespace: payment-service
automountServiceAccountToken: false
Step 2: Create named service accounts with explicit permissions.
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-api
namespace: payment-service
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payment-api-role
namespace: payment-service
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
resourceNames: ["payment-config"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-api-binding
namespace: payment-service
subjects:
- kind: ServiceAccount
name: payment-api
roleRef:
kind: Role
name: payment-api-role
apiGroup: rbac.authorization.k8s.io
This service account can read exactly one ConfigMap by name. An attacker who compromises the payment-api pod cannot list secrets, cannot create pods, cannot read any other resource. This is the principle of least privilege applied to Kubernetes.
Real Attack Scenario: Privilege Escalation via Misconfigured RBAC
An attacker compromises a pod running with a service account that has pods/exec and pods/create permissions. Here is the escalation path:
# 1. Create a privileged pod on a control plane node
kubectl run evil-pod --image=alpine --restart=Never \
--overrides='{"spec":{"nodeName":"control-plane-node","containers":[{"name":"evil","image":"alpine","command":["nsenter","--target","1","--mount","--","bash"],"securityContext":{"privileged":true}}],"hostNetwork":true}}'
# 2. Now the attacker has root on the control plane node
# 3. Extract etcd client certificates
# 4. Direct etcd access = full cluster control
This is not theoretical. This exact path is documented in real-world Kubernetes security incidents. The defense:
- Never grant
pods/execorpods/createto application service accounts - Restrict
nodeNamein Pod Security Admission - Enable audit logging to detect this attack in progress
Audit Logging: Who Did What When
Audit logging answers "who accessed what" — essential for both security investigations and RBAC hygiene. Enable it on the API server:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all RBAC changes at RequestResponse level
- level: RequestResponse
verbs: ["create", "update", "patch", "delete"]
resources:
- group: "rbac.authorization.k8s.io"
resources: ["*"]
# Log secret access at Metadata level
- level: Metadata
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["secrets"]
# Log pod exec at RequestResponse level
- level: RequestResponse
verbs: ["create"]
resources:
- group: ""
resources: ["pods/exec"]
# Don't log read-only system traffic
- level: None
userGroups: ["system:nodes"]
verbs: ["get", "list", "watch"]
Configure the API server to use this policy:
# kube-apiserver manifest snippet
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
Forward audit logs to your SIEM. When someone accesses a secret or modifies an RBAC rule, you want an alert — not a log file you check after the breach.
RBAC for Platform Teams: Multi-Tenant Patterns
Platform teams supporting multiple application teams need namespace-level isolation with shared platform services. The pattern: one namespace per team, ClusterRole for shared access:
# Platform ClusterRole — reusable across namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: team-developer
rules:
- apiGroups: ["apps", ""]
resources: ["deployments", "pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["networking.k8s.io"]
resources: ["networkpolicies"]
verbs: ["get", "list"]
---
# Bind per team namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-developers
namespace: team-a
subjects:
- kind: Group
name: team-a-devs
roleRef:
kind: ClusterRole
name: team-developer
apiGroup: rbac.authorization.k8s.io
Each team gets the same ClusterRole template scoped to their namespace. No team can access another team's resources. Platform teams maintain one ClusterRole instead of N identical Roles.
Validating RBAC Configurations
Before applying RBAC, validate:
# Can user alice create deployments in team-a namespace?
kubectl auth can-i create deployments --as alice --namespace team-a
# Can service account payment-api list secrets?
kubectl auth can-i list secrets --as system:serviceaccount:payment-service:payment-api -n payment-service
# What can this service account actually do?
kubectl auth can-i --list --as system:serviceaccount:payment-service:payment-api
Integrate this into CI: after every RBAC change, run kubectl auth can-i checks in a pipeline to validate that permissions match intent.
RBAC and Kubernetes Security: The Big Picture
RBAC is one layer of the defense-in-depth strategy covered in our Kubernetes security best practices guide. Combine it with:
- Pod Security Admission — enforce
restrictedprofile so pods cannot run as root - NetworkPolicy — deny-all by default, whitelist required traffic
- OPA/Gatekeeper — validate RBAC manifests in CI, block
cluster-adminbindings
For GPU-powered workloads, the same RBAC patterns protect inference clusters. See our Kubernetes LLM inference guide for securing multi-tenant GPU clusters with RBAC and network isolation.
RBAC is not a checkbox. It is an ongoing discipline. Audit your bindings monthly. Rotate service accounts. Grant exactly the permissions needed — and nothing more.