Skip to main content
Course/Security & Scale/RBAC (Role-Based Access Control)
1/875 min
Security & Scale

RBAC (Role-Based Access Control)

Secure your cluster with Roles, ClusterRoles, and ServiceAccounts β€” granting only the permissions each workload actually needs.

intermediate75 minRead β†’ Lab β†’ Quiz β†’ Practice
🧠 Brain Warm-Up
🧠 Brain Warm-Up: If Kubernetes has no built-in database for human users, how does the API server actually authenticate and authorize a kubectl command run by a cluster administrator? How are permissions bound to a ServiceAccount under the hood? Think about this security handshake before reading.

Kubernetes Has No Built-In User Accounts

Unlike database systems or operating systems, Kubernetes has no built-in user database. There is no kubectl create user command, and human user objects do not exist in etcd. Human identity is decoupled and comes from:

  • x509 client certificates: The API server authenticates requests by validating a client certificate signed by the cluster root Certificate Authority (CA). The Certificate's Common Name (CN) is parsed as the username, and the Organization (O) fields are mapped to groups (e.g., system:masters).
  • OIDC tokens: The API server authenticates JWTs from an external identity provider (like Dex, Okta, Keycloak, Google, or Azure AD) configured via api-server flags (--oidc-issuer-url, etc.).
  • Static token files / Webhooks: The API server delegates authentication to an external HTTPS webhook or reads from a local CSV token file.

Kubernetes does, however, have a first-class, etcd-persisted object for workload identity: the ServiceAccount.

ServiceAccount

A ServiceAccount is a namespaced identity for Pods (not humans). Every namespace has a default ServiceAccount that all Pods use unless you specify otherwise.

spec:
  serviceAccountName: app-reader

Starting in Kubernetes 1.24+, tokens are no longer statically stored as Secret objects in etcd. Instead, they are dynamic, short-lived (audience-bound and time-bound) JWTs generated on-demand by the TokenRequest API. The kubelet automatically projects this token into the Pod container at /var/run/secrets/kubernetes.io/serviceaccount/token using a projected volume mounted on a tmpfs (RAM disk) to prevent local disk exposure.

Visualizing RBAC Authorization Flow

HTTP/gRPC Request (kubectl or Pod)

β”‚

β–Ό

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”

β”‚ KUBERNETES API SERVER β”‚

β”‚ β”‚

β”‚ [Phase 1: Authentication (Authn)] β”‚

β”‚ - X.509 client certificates (signed by Cluster CA) β”‚

β”‚ - OIDC / Webhook tokens (e.g., Dex, Okta, Azure AD) β”‚

β”‚ - ServiceAccount JWT (TokenRequest API projected volume) β”‚

β”‚ β”‚ β”‚

β”‚ β–Ό (Identity Resolved) β”‚

β”‚ β”‚

β”‚ [Phase 2: Authorization (Authz)] β”‚

β”‚ - RBAC Engine: Evaluates Rules & Bindings β”‚

β”‚ - Subjects (User / Group / ServiceAccount) β”‚

β”‚ - API Group & Resource mapping (e.g., apiGroups: [""]) β”‚

β”‚ - Verbs validation (get, list, watch, create, update...) β”‚

β”‚ β”‚ β”‚

β”‚ β–Ό (Access Granted) β”‚

β”‚ β”‚

β”‚ [Phase 3: Admission Control] β”‚

β”‚ - Mutating & Validating Webhooks β”‚

β”‚ β”‚ β”‚

β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

β–Ό

[etcd Storage]

(Raft consensus commit)

Roles and ClusterRoles

Permissions are strictly additive (whitelist-only); there are no deny rules.

ObjectScopeUse case
RoleNamespace-scopedGrant access to resources inside a single namespace
ClusterRoleCluster-scopedAccess across all namespaces, or cluster-scoped resources (Nodes, PVs, namespaces)

A Role defines what is allowed β€” resources, API groups, and verbs:

rules:
- apiGroups: [""] # "" indicates the core API group
  resources: ["pods", "pods/status", "pods/log"] # resources and subresources
  verbs: ["get", "list", "watch"]

Available verbs: get (fetch specific resource), list (list resources), watch (stream real-time changes), create (POST new), update (PUT replace), patch (PATCH modify), delete (DELETE single), deletecollection (DELETE bulk).

API request paths map to resources:

  • Core group: /api/v1/namespaces/default/pods
  • Named group: /apis/apps/v1/namespaces/default/deployments

RoleBindings and ClusterRoleBindings

A RoleBinding attaches a Role (or ClusterRole) to a subject β€” a User, Group, or ServiceAccount β€” within one namespace.

A ClusterRoleBinding attaches a ClusterRole to a subject cluster-wide.

Role ──────────────────────── RoleBinding ──── Subject (ServiceAccount / User)
ClusterRole ──── ClusterRoleBinding ──── Subject (cluster-wide)
ClusterRole ──── RoleBinding ──── Subject (restricted to RoleBinding's namespace)
ℹ️ NOTE
Binding a ClusterRole using a RoleBinding is a common best practice. It allows you to reuse standard, cluster-wide defined ClusterRoles (like the built-in view or edit) while restricting the subject's access strictly to the namespace of the RoleBinding.

Principle of Least Privilege & SubjectAccessReview

Grant only the minimum permissions required. Common security risks include:

  • Using ClusterRoleBinding when a namespaced RoleBinding is sufficient.
  • Granting * wildcard verbs on * wildcard resources.
  • Granting update permissions on security-sensitive resources like secrets or pods/exec.

Under the hood, authorization checks are evaluated by the API server using a SubjectAccessReview API call. You can run this check locally using:

kubectl auth can-i list pods --as=system:serviceaccount:default:app-reader