Chapter 17 — Kubernetes RBAC and identity
In one sentence
Kubernetes RBAC is a graph of who-can-do-what-verb-on-what-resource, and a
handful of verbs and resources — create pods, get secrets, escalate,
bind, impersonate, and control over bindings or webhooks — are
privilege-escalation edges that turn a modest ServiceAccount into cluster-admin.
Where we are
Chapter 16 showed the boundaries; this chapter is the identity and authorization model that gates crossing them, and how it's abused.
How it works
Identities:
USERS / GROUPS not objects in the cluster -- they come from the
authenticator (client-cert CN/O, OIDC claims, an IdP). `system:masters`
group = hardcoded cluster-admin (bypasses RBAC) -> a client cert with
`O=system:masters` is a backdoor; never issue one, alert on any CSR
for it.
SERVICE ACCOUNTS namespaced objects. Pods run as one (`default` if
unset). Since 1.24, pods get a PROJECTED, BOUND token:
- audience-scoped (default: the API server)
- time-limited (~1h) and auto-rotated by the kubelet
- bound to the pod object (invalid once the pod is gone)
Legacy: `kubernetes.io/service-account-token` Secrets -- long-lived,
non-expiring. Discouraged; audit for them.
`automountServiceAccountToken: false` on the SA or pod if it doesn't
call the API (most workloads don't).
RBAC objects:
Role / ClusterRole a set of rules: {apiGroups, resources,
verbs, resourceNames?}. ClusterRole is
cluster-scoped or reusable across ns.
RoleBinding / ClusterRole-
Binding grants a Role/ClusterRole to
users/groups/SAs.
Verbs: get list watch create update patch delete deletecollection
+ SPECIAL: escalate, bind, impersonate, approve (CSR), use (PSP,
removed).
The escalation edges (audit for every one of these):
create/update/patch on PODS (or deployments/daemonsets/statefulsets/
jobs/cronjobs/replicasets/replicationcontrollers) ->
schedule a pod with: a more-powerful SA (`serviceAccountName`),
hostPath/hostPID/privileged, or a node-selector onto a
control-plane node -> node -> cluster. THE most common path.
pods/exec, pods/attach, pods/ephemeralcontainers -> get a shell in an
EXISTING privileged pod (e.g. a CNI or CSI DaemonSet) -> node.
get/list/watch on SECRETS -> read SA tokens (esp. in kube-system),
TLS keys, cloud creds. `secrets` + a namespace with powerful SAs =
escalation.
escalate on roles/clusterroles -> create a Role with MORE permissions
than you hold (normally blocked; this verb un-blocks it).
bind on roles/clusterroles -> bind a role you don't fully possess.
create on rolebindings/clusterrolebindings -> bind yourself (or a SA
you control) to `cluster-admin`.
impersonate (users/groups/serviceaccounts) -> `kubectl --as
system:masters` -> done.
certificatesigningrequests + approve + a signer -> mint a client cert
for ANY identity, including `O=system:masters`.
create on serviceaccounts/token (TokenRequest) -> mint a token for a
more-powerful SA.
control of validating/mutatingwebhookconfigurations -> see/rewrite all
API traffic (Ch 16).
patch/update on nodes (or nodes/status) -> influence scheduling; with
the right controller, more.
'get' on 'pods/log' broadly -> not escalation, but wide info
disclosure (secrets printed in logs).
Control over a namespace a privileged CONTROLLER watches (e.g. it
creates SAs/bindings from CRs there) -> indirect escalation.
Defence:
[ ] Least privilege: Role (namespaced) over ClusterRole; `resourceNames`
to pin to specific objects; never `verbs: ["*"]` / `resources:
["*"]` / `apiGroups: ["*"]` outside genuine admin.
[ ] No workload SA gets: secrets (cross-namespace especially),
pods/exec, create on pods/bindings/CSRs, escalate/bind/impersonate,
webhook configs.
[ ] `automountServiceAccountToken: false` by default (a policy/mutation).
[ ] Ban `O=system:masters` client certs; alert on any CSR mentioning it
or auto-approving.
[ ] Humans get access via OIDC (SSO) mapped to Roles, short-lived,
ideally JIT -- no static admin kubeconfigs.
[ ] Audit continuously: `kubectl auth can-i --list --as=...`,
`rakkess` / `kubectl-who-can` / `access-matrix`, `KubiScan`,
`rbac-police`, `KubeHound` (graphs the escalation edges above).
[ ] Review the default ClusterRoles bound to `system:` groups and any
broad ClusterRoleBinding to `system:authenticated` or
`system:serviceaccounts`.
Worked example
create deployments in one namespace to cluster-admin.
GIVEN: SA `core:deployer` -> Role in `core`: create/update/patch/delete
on deployments, replicasets, pods. Intended: let the deploy job roll
out `core` services. No secrets, no exec, no bindings.
ESCALATION:
1. `core` also runs `some-operator` whose SA `core:operator` is bound
(cluster-wide) to a ClusterRole with `secrets` + `clusterrole-
bindings` create (a lazily-scoped operator -- extremely common).
2. `core:deployer` creates a Deployment whose pod template sets
`serviceAccountName: operator` and runs `kubectl` (or curls the
API with the projected token).
3. That pod now acts as `core:operator`: it reads any Secret and
creates a ClusterRoleBinding granting `cluster-admin` to
`core:deployer` (or a fresh SA).
4. Cluster-admin.
CUTS:
- `core:deployer`'s Role must NOT allow setting an arbitrary
`serviceAccountName`. Use a policy (Kyverno/Gatekeeper/VAP) that
denies pods referencing a SA other than an allowlist for the
creator, OR give `deployer` only `patch` on specific named
Deployments (`resourceNames`) so it can't create new ones with a
chosen SA.
- Fix `core:operator`: scope it to the exact resources/namespaces it
manages; no blanket `secrets` or `clusterrolebindings`.
- Policy: deny creating/patching `clusterrolebindings` to
`cluster-admin` except by a break-glass identity; alert on it.
- `automountServiceAccountToken: false` unless the workload calls the
API.
Practice (75 min) — kind cluster
- Create a namespace with a SA bound to a Role that has only
create pods. From a pod using that token, escalate to cluster-admin by scheduling a pod withserviceAccountNameset to a more powerful SA (create one:secrets-reader inkube-system). Confirmauth can-i --listshows the jump. - Now do it via
pods/execinto an existing privileged DaemonSet pod (deploy a fake "cni" DaemonSet withhostPath: /). - Run
KubeHoundagainst the cluster and confirm it graphs the paths you just walked. Runrbac-policeandKubiScan. - Apply the cuts: a Kyverno policy restricting
serviceAccountNameon pod create; scope the operator SA;automountServiceAccountToken: falsedefault; denyclusterrolebindingstocluster-admin. Re-run KubeHound — paths gone. - Search the cluster for legacy
service-account-tokenSecrets and for anyClusterRoleBindingtosystem:authenticated/system:serviceaccounts. - Set up OIDC auth (Dex + kind) so humans get short-lived, group-mapped access instead of the admin kubeconfig.
Common mistakes
create pods(or any workload kind) treated as low-risk. It's a node/ cluster escalation edge viaserviceAccountName, hostPath, or node selectors.- Operators/controllers bound to
cluster-admin"to make it work". Scope them; they're a permanent escalation target. get secretsin a workload SA. Almost never needed; it reads other workloads' tokens.- Broad
ClusterRoleBindingtosystem:authenticatedor all serviceaccounts. Everyone/every pod gets those rights. - Client certs with
O=system:masters. A permanent RBAC-bypass backdoor. - Leaving
automountServiceAccountTokenon for everything. Free API credentials in every pod for an attacker who lands there. - RBAC reviewed once. It drifts with every new operator and Helm chart. Graph it in CI.
Check yourself
- How do bound projected SA tokens (1.24+) differ from legacy Secret tokens?
- Why is
create podsa privilege-escalation edge? Name two mechanisms. - What do the verbs
escalate,bind, andimpersonateeach allow? - Why is
O=system:mastersin a client cert dangerous? - Give three RBAC grants a normal workload SA should never have.
- What tools graph RBAC escalation paths?
Further reading
- Docs: Kubernetes "Using RBAC Authorization" (the "privilege escalation prevention" and "default roles" sections), "ServiceAccount tokens", "Bound Service Account Tokens".
- Analysis: "Bad Pods" and the "RBAC escalation" matrices (BishopFox, Aqua,
Palo Alto Unit 42);
rbac.devresources; the CNCFrbac-policeandKubiScanREADMEs. - Attack paths: KubeHound (Datadog) docs and the "Kubernetes attack graph" talks.
- Practice: KubernetesGoat "RBAC least privilege",
kctf, the "Kubernetes Lan Party" challenges.