Kubernetes RBAC Design

Design and implement role-based access control in Kubernetes that follows least privilege without paralyzing teams. Covers ClusterRoles, RoleBindings, namespace isolation, service accounts, admission webhooks, and RBAC troubleshooting patterns.

Kubernetes RBAC Design is the practice of structuring access control in a Kubernetes cluster using Role-Based Access Control (RBAC), where permissions are granted to users, service accounts, and groups through roles and bindings. It matters when multiple teams, environments, or automation systems need consistent, auditable, and scalable access to cluster resources—especially in production clusters with hundreds of pods, dozens of namespaces, and complex workloads.

Defining Roles and ClusterRoles

Roles and ClusterRoles define sets of permissions. A Role applies within a single namespace; a ClusterRole applies cluster-wide. Permissions are expressed as verbs (e.g., get, list, create, delete) on Kubernetes resources (e.g., pods, services, configmaps, deployments), optionally scoped to specific resource names or subresources.

A Role in default namespace defines access to pods and configmaps:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: default
  name: pod-configmap-reader
rules:
- apiGroups: [""]
  resources: ["pods", "configmaps"]
  verbs: ["get", "list", "watch"]

A ClusterRole grants broad access across the entire cluster:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "create", "delete", "patch"]
- apiGroups: ["apps"]
  resources: ["deployments", "statefulsets"]
  verbs: ["get", "list", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["get", "list", "create", "update"]
  resourceNames: ["app-config"]

The key difference lies in scope: Role rules apply only within a namespace, while ClusterRole rules apply to all namespaces and cluster-level resources. Misunderstanding this leads to roles that seem effective in one namespace but fail silently elsewhere.

Binding Roles: RoleBinding and ClusterRoleBinding

RoleBinding connects a Role to a subject (user, group, or service account) within a namespace. ClusterRoleBinding connects a ClusterRole to a subject across the entire cluster.

To grant the pod-configmap-reader Role to a user [email protected]:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: alice-pod-configmap-reader
  namespace: default
subjects:
- kind: User
  name: [email protected]
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-configmap-reader
  apiGroup: rbac.authorization.k8s.io

To grant the developer ClusterRole to a service account ci-runner across the entire cluster:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ci-runner-developer
subjects:
- kind: ServiceAccount
  name: ci-runner
  namespace: ci
roleRef:
  kind: ClusterRole
  name: developer
  apiGroup: rbac.authorization.k8s.io

The subjects field supports User, Group, and ServiceAccount, each with distinct identity formats. A User is a string like [email protected], a Group is a string like developers, and a ServiceAccount is a pair of name and namespace.

A common failure point is misconfiguring the apiGroup for RoleRef and subjects. If apiGroup is omitted, the system defaults to rbac.authorization.k8s.io, but this is often missed. A RoleBinding referencing a ClusterRole without setting apiGroup to rbac.authorization.k8s.io results in a role not found error.

Service Account Tokens and Authentication

Service accounts are the primary identity for workloads. When a Pod runs in a namespace, it can automatically use a service account via mounted tokens.

A Pod spec mounting the default service account:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  serviceAccountName: app-user
  containers:
  - name: app
    image: myapp:v1

The service account is automatically mounted at /var/run/secrets/kubernetes.io/serviceaccount with:

The JWT token can be presented to the Kubernetes API server via the Authorization: Bearer <token> header.

To access the API from within a Pod:

curl -H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
     --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
     https://kubernetes.default.svc/api/v1/pods

The token is signed with a key from the API server’s service-account-issuer and service-account-key-file, and includes claims such as iss, aud, exp, nbf, sub, and name. The sub claim holds the service account’s name and namespace.

A silent failure occurs when the service account’s imagePullSecret is not properly configured. If the imagePullSecret is missing or points to a non-existent secret, the Pod fails to pull the image, and the service account’s token is not available in the Pod.

Hierarchical Access with Role Aggregation

To build reusable, composable roles, cluster roles can be aggregated by referencing other cluster roles.

A developer role that aggregates pod-configmap-reader and deployment-manager:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: developer
aggregationRule:
  clusterRoleSelectors:
  - matchLabels:
      rbac.authorization.k8s.io/aggregate-to-developer: "true"
rules: []

Now, any ClusterRole with label rbac.authorization.k8s.io/aggregate-to-developer: "true" is automatically included in the developer role.

A pod-configmap-reader role:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: pod-configmap-reader
  labels:
    rbac.authorization.k8s.io/aggregate-to-developer: "true"
rules:
- apiGroups: [""]
  resources: ["pods", "configmaps"]
  verbs: ["get", "list", "watch"]

When the developer role is applied, its rules include all rules from pod-configmap-reader, deployment-manager, and any other role with that label.

This pattern enables modular design: teams can create role components, and central teams can define high-level roles that pull in subsets.

Auditing and Troubleshooting Access

Access failures in RBAC often surface as 403 Forbidden errors when accessing the API server. The error message includes the requested verb, resource, and namespace.

Example error from a Pod:

{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "forbidden: User \"system:serviceaccount:ci:ci-runner\" cannot get resource \"pods\" in API group \"\" in the namespace \"production\"",
  "reason": "Forbidden",
  "details": {
    "kind": "pods",
    "name": "",
    "group": "",
    "version": "",
    "uid": "",
    "causes": [
      {
        "reason": "PermissionDenied",
        "message": "User \"system:serviceaccount:ci:ci-runner\" cannot get resource \"pods\" in API group \"\" in the namespace \"production\""
      }
    ]
  },
  "code": 403
}

To debug, inspect the service account’s RoleBinding and ClusterRoleBinding:

kubectl auth can-i get pods --as=system:serviceaccount:ci:ci-runner -n production

The output is either yes or no, and can be detailed with --as and --namespace.

To list all roles and bindings affecting a service account:

kubectl auth can-i all --as=system:serviceaccount:ci:ci-runner -n production --v=6

This shows the full chain of permissions, including which role and binding contributed each permission.

Common Pitfalls

Advanced Patterns

For multi-tenant setups, use ClusterRole with resourceNames to isolate tenant access:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: tenant-admin
rules:
- apiGroups: [""]
  resources: ["namespaces"]
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["pods"]
  resourceNames: ["tenant-1", "tenant-2"]
  verbs: ["get", "create"]

This allows one admin to manage multiple namespaces but only access pods in two specific ones.

For fine-grained control, define subresources in rules:

rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "create"]
  subresources: ["scale", "status"]

This allows get and create on deployments, and get, update, and patch on deployments/scale and deployments/status.

Summary

Kubernetes RBAC design is the art of defining, binding, and auditing access using Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings. The system relies on the interplay between service account tokens, API server authentication, and label-based aggregation. Success depends on consistent use of apiGroup, correct subjects, and attention to resource scoping. Failures often appear as subtle 403 errors that require digging into the auth can-i command and the full cluster role hierarchy.

This page was rewritten on 15 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.