RBAC (Role-Based Access Control) #

In the Kubernetes ecosystem, cluster security heavily depends on our ability to control who can interact with the API Server and which actions they’re allowed to do. After a request successfully passes the authentication stage (identity verification), it must go through the authorization stage (access rights checking). Kubernetes provides a very robust, granular role-based authorization system called RBAC (Role-Based Access Control).

Applying secure RBAC isn’t just a functionality matter so our applications can run — it’s part of the cluster’s zero-trust foundation. Overly permissive RBAC configuration is often the number one security hole attackers exploit for lateral movement and privilege escalation after successfully breaking into one application Pod. This article dissects the RBAC object architecture, access subject differences, least privilege design tactics, and access escalation risk mitigation in production.


The API Server Request Flow Architecture #

Before studying RBAC objects in detail, let’s understand how the API Server processes every incoming HTTP request through the following sequence diagram:

sequenceDiagram
    participant Client as User / Pod ServiceAccount
    participant API as kube-apiserver
    participant AuthN as Authentication Filter
    participant AuthZ as Authorization Filter (RBAC)
    participant Admin as Admission Controller
    participant etcd as Cluster Storage (etcd)
    
    Client->>API: HTTP Request (e.g., GET /api/v1/pods)
    API->>AuthN: Verify Credentials (Token/Cert)
    AuthN-->>API: Verified Identity (User/Group/SA)
    API->>AuthZ: Check Access Rights (RBAC Engine)
    Note over AuthZ: Evaluate RoleBindings & Roles<br>against Subject, Resource, & Verb
    AuthZ-->>API: Access Allowed
    API->>Admin: Run Admission Control (Mutating/Validating)
    Admin-->>API: Request Accepted & Valid
    API->>etcd: Write/Read Data
    etcd-->>API: Return Data
    API-->>Client: HTTP Response 200 OK

Dissecting the Four Main RBAC Components #

The Kubernetes RBAC system is declared through relational links between four main API objects: Subjects, Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings.

1. Subjects (Who Is Requesting Access?) #

Subjects are the entities requesting access to the API Server. There are three subject categories in Kubernetes:

  • ServiceAccount: Identity accounts created inside the Kubernetes cluster for use by running application Pods. This is the subject we most often manage when writing manifests.
  • User (Human Users): Identified using external certificates (X.509 Client Certs) or Identity Provider integration (OIDC/SSO). Kubernetes has no internal database for storing User objects.
  • Group: A logically grouped collection of subjects, e.g. system:serviceaccounts (all ServiceAccounts in the cluster) or corporate LDAP groups.

2. Roles vs ClusterRoles (What Access Rights Are Granted?) #

These objects define permission rule lists containing combinations of API Groups, Resources, and Verbs (actions like get, list, create, update, delete).

  • Role: Local and isolated to one specific namespace. Roles can only restrict access to resources inside that namespace (e.g. Pods, Services, ConfigMaps).
  • ClusterRole: Cluster-wide global (non-namespaced). ClusterRoles can control access to cluster-level resources (like Nodes, PersistentVolumes, Namespaces) or uniformly control access to namespaced resources across all namespaces in the cluster.

3. RoleBindings vs ClusterRoleBindings (How Do We Connect Them?) #

Binding objects act as bridges connecting Subjects with Roles/ClusterRoles.

  • RoleBinding: Attaches a Role (or ClusterRole) to a Subject within a specific namespace. If we attach a ClusterRole using a RoleBinding, the Subject only gets access within the namespace where that RoleBinding is deployed.
  • ClusterRoleBinding: Attaches a ClusterRole to a Subject at the global cluster level. The Subject gets access to those resources across all namespaces without restrictions.
flowchart TD
    subgraph Subjects["Subjects (Who?)"]
        SA["ServiceAccount"]
        US["User / Group"]
    end
    
    subgraph Bindings["Bindings (Relationship)"]
        RB["RoleBinding (Namespaced)"]
        CRB["ClusterRoleBinding (Global)"]
    end
    
    subgraph Access["Access Definition (Permissions)"]
        R["Role (Limited Location)"]
        CR["ClusterRole (Global / Cluster-wide)"]
    end
    
    SA --> RB
    US --> CRB
    
    RB --> R
    RB -.->|"Restricts Access to a Namespace"| CR
    CRB --> CR

Practical Implementation: Designing Least Privilege Access #

To keep the cluster secure, we must always apply the Principle of Least Privilege. Never use the built-in admin or cluster-admin ClusterRoles for our application Pods if the application only needs simple read access.

Let’s study a concrete implementation example for the log-archiver microservice needing access rights to read (get, list, watch) Pod log data in the logging namespace.

1. Application-Specific ServiceAccount Manifest #

apiVersion: v1
kind: ServiceAccount
metadata:
  name: log-archiver-sa
  namespace: logging

2. Role Manifest (Restricting Permissions to Pod Reads Only) #

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader-role
  namespace: logging # Isolated within this namespace
rules:
- apiGroups: [""] # "" denotes the core API group
  resources: ["pods", "pods/log"] # Only allow access to Pod resources and the log sub-resource
  verbs: ["get", "list", "watch"] # Read-only actions, 'create', 'update', or 'delete' forbidden

3. RoleBinding Manifest (Connecting the ServiceAccount with the Role) #

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: log-archiver-binding
  namespace: logging
subjects:
- kind: ServiceAccount
  name: log-archiver-sa
  namespace: logging
roleRef:
  kind: Role
  name: pod-reader-role
  apiGroup: rbac.authorization.k8s.io

4. Deployment Manifest (Consuming the ServiceAccount) #

apiVersion: apps/v1
kind: Deployment
metadata:
  name: log-archiver
  namespace: logging
spec:
  replicas: 1
  selector:
    matchLabels:
      app: log-archiver
  template:
    metadata:
      labels:
        app: log-archiver
    spec:
      # Inject the dedicated ServiceAccount identity into the Pod
      serviceAccountName: log-archiver-sa
      containers:
      - name: archiver
        image: company/log-archiver:v1.0.0

Verifying and Auditing RBAC Access Rights #

As cluster operators, we must frequently audit to ensure no access right deviations. Kubernetes provides a very powerful built-in CLI command, kubectl auth can-i, to test RBAC rules without switching account contexts.

1. Testing Access for Yourself #

# Am I allowed to create deployments in the default namespace?
kubectl auth can-i create deployments --namespace default
# Output: yes or no

2. Testing Access on Behalf of a Specific ServiceAccount (Impersonation) #

Very useful for validating whether the ServiceAccount we designed is truly restricted.

# Test whether the log-archiver-sa ServiceAccount can delete Pods in the logging namespace
kubectl auth can-i delete pods \
  --as=system:serviceaccount:logging:log-archiver-sa \
  --namespace logging
# Expected output: no (because it's only given get/list/watch permissions)

# Test whether that ServiceAccount can read Pod logs
kubectl auth can-i get pods/log \
  --as=system:serviceaccount:logging:log-archiver-sa \
  --namespace logging
# Expected output: yes

3. Auditing Permissions Using External Tooling #

For large production clusters, auditing manually one by one is very tiring. We can use open-source tools like rakess (Access Matrix Visualizer) or krane to map RBAC relationships and automatically detect ServiceAccounts with overly broad permissions.


Privilege Escalation Risk Mitigation #

Kubernetes has internal security systems preventing subjects from manipulating RBAC rules to raise their own access rights. This protection is governed by two main rules:

1. The No Escalation Rule #

The API Server won’t allow a user to create or update Roles/ClusterRoles containing permissions above the permissions that user currently holds.

  • Scenario: Developer A only has ConfigMap management permissions. Developer A can’t create a new Role containing Pod creation permissions, even though Developer A has write access to Role objects. If they try, the API Server rejects it with a 403 Forbidden: privilege escalation is not allowed error.

2. The Special bind and escalate Permission Rules #

If an application (like a CI/CD Controller or Operator) is designed to create and attach new Roles to other ServiceAccounts, that application’s ServiceAccount must be explicitly given the bind or escalate verb permissions on roles resources in its ClusterRole.

# Example of giving a CI/CD controller limited permission to attach Roles
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cicd-role-binder
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles", "rolebindings"]
  verbs: ["get", "list", "create", "update"]
- apiGroups: ["rbac.authorization.k8s.io"]
  resources: ["roles"]
  verbs: ["bind"] # ← Allows attaching Roles without triggering the privilege escalation block

Anti-Patterns vs Best Solutions #

Let’s study fatal mistakes developer teams often make when configuring RBAC in Kubernetes along with their fixes.

Anti-Pattern 1: Using the * Wildcard on Verbs and Resources #

The lazy act of using asterisks (*) on resources or verbs to speed up deployments without thinking about security aspects. This grants full administrator permissions on the related objects.

# ✗ ANTI-PATTERN: Wildcard usage granting unlimited access
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-manager-role
  namespace: billing
rules:
- apiGroups: ["*"] # Allows all API groups
  resources: ["*"] # Allows all resources (Pods, Secrets, PVs, etc.)
  verbs: ["*"]     # Allows all actions (create, delete, etc.)

Best Solution #

Write the resource and verb lists explicitly and granularly. Only include what the application code actually needs during operation.

# ✓ SOLUTION: Declare resources and verbs in detail
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-manager-role
  namespace: billing
rules:
- apiGroups: [""]
  resources: ["configmaps", "services"] # Only restrict to these two objects
  verbs: ["get", "list", "update"] # Restrict data manipulation actions, 'delete' forbidden

Anti-Pattern 2: Using ClusterRoleBindings for Namespaced Resources #

Attaching built-in ClusterRoles (like view or edit) to ServiceAccounts using ClusterRoleBinding, even though the application only runs and interacts within one specific namespace.

Consequences #

If our application suffers a security leak (exploited), attackers can use that ServiceAccount token to read or modify sensitive data across all other namespaces in the cluster, destroying the security environment isolation (the blast radius expands across the entire cluster).

Best Solution #

Always use RoleBinding (not ClusterRoleBinding) to restrict that ClusterRole’s operational scope to only the application’s target namespace.

# ✓ SOLUTION: Binding a ClusterRole in a limited way using a RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: api-viewer-binding
  namespace: payment # Permission only applies within the 'payment' namespace
subjects:
- kind: ServiceAccount
  name: payment-api-sa
  namespace: payment
roleRef:
  # References a built-in Kubernetes ClusterRole, but bound locally
  kind: ClusterRole 
  name: view 
  apiGroup: rbac.authorization.k8s.io

Anti-Pattern 3: Giving Unfiltered get, list, watch Permissions on Secrets #

Giving full read access to all Secret objects in one namespace to an application that actually only needs access to one specific Secret of its own.

Consequences #

If the application container gets exploited, attackers can pull all Secrets in that namespace, including main database credentials, Slack tokens, or other third-party keys.

Best Solution #

Use the resourceNames feature on the Role manifest to exclusively restrict the ServiceAccount’s read access to only the predetermined Secret object names.

# ✓ SOLUTION: Restrict Secret access using resourceNames
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: payment-secret-reader
  namespace: payment
rules:
- apiGroups: [""]
  resources: ["secrets"]
  # Restricts read access to only the objects named below
  resourceNames: ["payment-db-secret", "stripe-keys"] 
  verbs: ["get"] # Only allow 'get' ('list' or 'watch' forbidden to prevent scraping)

Best Production RBAC Manifests (Audit-Hardened) #

Here’s a combined production-ready manifest example applying high RBAC security standards, complete with namespace restrictions, resourceNames usage for Secrets, and ServiceAccount isolation:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: user-portal-sa
  namespace: user-management
  labels:
    app.kubernetes.io/name: user-portal
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: user-portal-role
  namespace: user-management
  labels:
    app.kubernetes.io/name: user-portal
rules:
# 1. Allow reading its own Pod metadata for clustering
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
# 2. Allow targeted Secret reads (only for database credentials)
- apiGroups: [""]
  resources: ["secrets"]
  resourceNames: ["db-user-portal-secret"]
  verbs: ["get"]
# 3. Allow updating dynamic configuration ConfigMap status
- apiGroups: [""]
  resources: ["configmaps"]
  resourceNames: ["user-portal-dynamic-flags"]
  verbs: ["get", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: user-portal-binding
  namespace: user-management
  labels:
    app.kubernetes.io/name: user-portal
subjects:
- kind: ServiceAccount
  name: user-portal-sa
  namespace: user-management
roleRef:
  kind: Role
  name: user-portal-role
  apiGroup: rbac.authorization.k8s.io

RBAC Access Rights Audit Checklist #

Make sure our cluster’s RBAC configuration meets the following security audit standards before production releases:

SERVICEACCOUNT ISOLATION:
  □ Every microservice has its own unique ServiceAccount (not using the 'default' ServiceAccount).
  □ The 'automountServiceAccountToken: false' property is enabled on Pods that don't need API Server communication.
  □ ServiceAccount access permissions are tested using the 'kubectl auth can-i --as=...' command.

ACCESS RIGHT GRANULARITY:
  □ All Role manifests are free of '*' wildcards on verbs and resources.
  □ Access to Secret objects is strictly restricted using the 'resourceNames' property.
  □ Read verb rules are limited to only 'get' (avoid 'list' or 'watch' if not needed).

BINDING SCOPE:
  □ RoleBindings are used (not ClusterRoleBindings) for namespaced resources to shrink the blast radius.
  □ ClusterRoleBindings are only used for system operators or cluster-wide controllers (e.g. Ingress Controllers, Prometheus).
  □ ClusterRoleAdmin-level access permissions are restricted to platform administrator teams only.

Summary #

  • Respect the least privilege principle — Always restrict container ServiceAccount access permissions to only the objects and actions the application code actually needs.
  • Use RoleBindings for namespaced resources — Limit built-in ClusterRole power scope using RoleBindings so access rights only apply locally within one target namespace.
  • Lock Secret access with resourceNames — Prevent attackers from mass-reading (scraping) all Secrets in a namespace by restricting queries to only specific object names.
  • Create unique ServiceAccounts per application — Never share one global ServiceAccount across many different applications to avoid access right contamination.
  • Avoid * wildcards in production — Declare API groups, resources, and verbs explicitly and in detail in Role/ClusterRole manifests.
  • Do periodic audits — Leverage the kubectl auth can-i command with the --as impersonation parameter to simulate and verify RBAC sharpness.

← Previous: Deployment Anti-Patterns   Next: Pod Security →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact