devops

Kubernetes RBAC Deep Dive

Kubernetes RBAC deep dive: ClusterRole vs Role, audit logging, service account hardening, and defense against privilege escalation — real YAML examples.

June 29, 2026·6 min read·
#kubernetes#rbac#security#devops#clusterrole#audit-logging#pod-security

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/exec or pods/create to application service accounts
  • Restrict nodeName in 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 restricted profile so pods cannot run as root
  • NetworkPolicy — deny-all by default, whitelist required traffic
  • OPA/Gatekeeper — validate RBAC manifests in CI, block cluster-admin bindings

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.

#kubernetes#rbac#security#devops#clusterrole#audit-logging#pod-security
D
DevToCashAuthor

Senior DevOps/SRE Engineer · 10+ years · Professional Trader (IDX, Crypto, US Equities)

I write about real infrastructure patterns and trading strategies I use in production and in live markets. No courses, no affiliate hype — just documentation of what actually works.

More about me →