open atlas
↑ Back to track
Cloud & Infra Security CLOUD · 02 · 03

Kubernetes RBAC

Kubernetes RBAC is deny-by-default, but the foot-guns are in the grants: a wildcard verb, a Role that can create pods, or a stray cluster-admin binding quietly equals full-cluster takeover. Least privilege here is a verb-by-verb discipline, not a checkbox.

CLOUD Middle ◷ 15 min
Level
FoundationsJuniorMiddleSenior

A teammate is debugging a flaky deploy and the fastest way to make the error go away is a one-liner everyone has typed at 2 a.m.: kubectl create clusterrolebinding ci-debug --clusterrole=cluster-admin --serviceaccount=ci:runner. The pipeline goes green, the ticket closes, nobody reverts it. Six months later that CI runner’s pod is popped through an unrelated app bug — and because its service-account token is mounted at /var/run/secrets/kubernetes.io/serviceaccount/token and bound to cluster-admin, the attacker doesn’t get one pod. They get every secret in every namespace, the ability to schedule a privileged pod on any node, and a path to the underlying host. RBAC was on the whole time. It said “yes” because someone told it to.

By the end of this lesson you’ll be able to read a Role/RoleBinding pair and say exactly what it grants, spot the three bindings that silently equal cluster takeover, and design a service-account that can do its job and nothing else.

The four nouns, and why two of them are the trap

Kubernetes RBAC is built from exactly four object kinds, and the entire security model lives in how you combine them.

  • A Role is a namespaced list of rules: tuples of apiGroups, resources, and verbs (get, list, watch, create, update, patch, delete). A Role grants nothing until it is bound.
  • A ClusterRole is the same shape but cluster-scoped — it can name cluster-wide resources (nodes, persistentvolumes), and it can be reused across namespaces.
  • A RoleBinding attaches a Role (or a ClusterRole) to a subject inside one namespace.
  • A ClusterRoleBinding attaches a ClusterRole to a subject across the entire cluster.

The subject is usually a ServiceAccount — the identity every pod runs as. Here is the part people miss: by default each pod gets a service-account token mounted into its filesystem, and that token is the pod’s credential to the API server. So the blast radius of any RBAC grant is “whatever code an attacker can get running in a pod that uses that service account.” RBAC is genuinely deny-by-default — a brand-new service account can do essentially nothing — but the danger isn’t the default. It’s the grants you add, because two combinations turn a small mistake into total compromise: a ClusterRoleBinding (cluster-wide, not one namespace) and the built-in cluster-admin ClusterRole (every verb on every resource, including *).

The cluster-admin foot-guns, ranked

The reason RBAC incidents look identical across companies is that the same three patterns keep appearing. Here is what each one actually grants, why it’s tempting, and the safe replacement.

Foot-gunWhat it really grantsWhy it happensLeast-privilege fix
cluster-admin bindingEvery verb on every resource, all namespaces — full takeover”Make the error go away” debugging; copy-pasted Helm valuesA scoped Role with the 3-4 verbs actually used
Wildcard verb/resource (*)Today’s resources and every future one the operator adds”I don’t know the exact verbs yet, I’ll tighten later”Enumerate verbs explicitly; never ship verbs: [”*“]
create pods / pods/execSchedule a privileged pod or exec in — pivot to host/other SAsLooks like a normal app verb; reviewers wave it throughTreat pod-create as privileged; gate with Pod Security + policy
get/list secretsRead every secret in scope, including other SAs’ tokens”The app needs one secret” → granted on the whole verbScope by resourceNames; prefer projected/CSI secrets
escalate / bind on rolesGrant yourself permissions you don’t have — privilege escalationHidden in an operator’s ClusterRole nobody auditsNever grant escalate/bind to workloads

The throughline: RBAC has no deny rule. You cannot “subtract” a permission after the fact — the union of all bound rules is what the subject can do, and if any rule says yes, it’s yes. That is why over-granting is so dangerous: there’s no safety net rule to catch you, so least privilege has to be enforced at write time, verb by verb. The aggregation behavior makes this worse: ClusterRoles labeled rbac.authorization.k8s.io/aggregate-to-edit: "true" get automatically merged into the built-in edit role, so a custom resource’s operator can silently widen what edit-bound users can do without you touching your bindings.

Common mistake

The most common real-world escalation isn’t cluster-admin directly — it’s create pods combined with no Pod Security restriction. If a service account can create a pod, an attacker who owns that SA can schedule a pod with hostPath: / mounted, or hostPID: true, read the kubelet’s credentials off the node, and pivot to every workload on that node — including ones with far more powerful tokens. That’s why “can this SA create pods?” is the single highest-signal question in an RBAC review, and why pod-create must be paired with the Pod Security Admission restricted profile to mean anything.

Designing a least-privilege service account

The senior reflex is to build grants bottom-up from observed need, not top-down from convenience. The workflow:

  1. Start from deny. A new ServiceAccount has no permissions. Resist the urge to bind a broad built-in role “to get unblocked.”
  2. Enumerate the actual verbs. Run the workload, capture which API calls it makes (audit logs, or kubectl auth can-i --list), and grant exactly those — get/list/watch on the specific resources, nothing wildcarded.
  3. Prefer Role over ClusterRole, RoleBinding over ClusterRoleBinding. Keep the grant in one namespace unless the resource is genuinely cluster-scoped. A namespaced grant caps the blast radius at one namespace.
  4. Scope secrets by resourceNames. If the app needs one secret, name it; don’t grant the whole secrets verb.
  5. Turn off the token mount when unused. Set automountServiceAccountToken: false on pods that never call the API server — most app pods don’t, and an unmounted token is a credential that can’t be stolen.
  6. Audit with kubectl auth can-i. Before shipping, ask the cluster directly: kubectl auth can-i create pods --as=system:serviceaccount:app:web -n app should return no.
Pick the best fit

A backend pod needs to read one ConfigMap and one Secret in its own namespace to boot. CI is red because the SA can't read them. Pick the correct grant.

Quiz

A Role grants `verbs: [list]` on `secrets` in one namespace, bound to a pod's ServiceAccount. The pod is compromised. What can the attacker do with the mounted token?

Quiz

Why is `create pods` such a high-signal permission to flag in an RBAC review, even without an explicit cluster-admin binding?

Order the steps

Order these steps for designing a least-privilege service account, from first to last:

  1. 1 Start from deny — a fresh ServiceAccount with no bindings
  2. 2 Enumerate the exact verbs the workload actually calls
  3. 3 Grant a namespaced Role (prefer Role over ClusterRole), scoped by resourceNames
  4. 4 Bind it with a RoleBinding to that one ServiceAccount
  5. 5 Verify with `kubectl auth can-i` that nothing extra is allowed
Recall before you leave
  1. 01
    Walk through how a single cluster-admin ClusterRoleBinding on a CI service account becomes full-cluster compromise when one pod is breached.
  2. 02
    Why is `list secrets` more dangerous than it looks, and what's the least-privilege alternative?
Recap

Kubernetes RBAC is assembled from four objects — Role and ClusterRole (the verb/resource rules) plus RoleBinding and ClusterRoleBinding (which attach them to a subject, usually a ServiceAccount). The model is genuinely deny-by-default and allow-only: a fresh SA can do nothing, and the union of every bound rule is what its auto-mounted token can do, with no deny rule to claw a grant back. That’s why the foot-guns are all over-grants, not defaults: a cluster-admin ClusterRoleBinding (every verb everywhere), a wildcard verb that also covers future resources, create pods (schedule a privileged pod, pivot to the node), list secrets (read every secret including other tokens), and escalate/bind (grant yourself more). Least privilege is a write-time discipline — start from deny, enumerate the real verbs, prefer namespaced Role + RoleBinding, scope secrets by resourceNames, turn off the token mount when unused, and verify with kubectl auth can-i. Now when you see a binding in a review, your first question is: what’s the subject, is it cluster-wide, and can it create pods or read secrets?

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 6 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.