Frontend Labs
⋯

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

  1. 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 with serviceAccountName set to a more powerful SA (create one: secrets-reader in kube-system). Confirm auth can-i --list shows the jump.
  2. Now do it via pods/exec into an existing privileged DaemonSet pod (deploy a fake "cni" DaemonSet with hostPath: /).
  3. Run KubeHound against the cluster and confirm it graphs the paths you just walked. Run rbac-police and KubiScan.
  4. Apply the cuts: a Kyverno policy restricting serviceAccountName on pod create; scope the operator SA; automountServiceAccountToken: false default; deny clusterrolebindings to cluster-admin. Re-run KubeHound — paths gone.
  5. Search the cluster for legacy service-account-token Secrets and for any ClusterRoleBinding to system:authenticated / system:serviceaccounts.
  6. 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 via serviceAccountName, hostPath, or node selectors.
  • Operators/controllers bound to cluster-admin "to make it work". Scope them; they're a permanent escalation target.
  • get secrets in a workload SA. Almost never needed; it reads other workloads' tokens.
  • Broad ClusterRoleBinding to system:authenticated or all serviceaccounts. Everyone/every pod gets those rights.
  • Client certs with O=system:masters. A permanent RBAC-bypass backdoor.
  • Leaving automountServiceAccountToken on 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

  1. How do bound projected SA tokens (1.24+) differ from legacy Secret tokens?
  2. Why is create pods a privilege-escalation edge? Name two mechanisms.
  3. What do the verbs escalate, bind, and impersonate each allow?
  4. Why is O=system:masters in a client cert dangerous?
  5. Give three RBAC grants a normal workload SA should never have.
  6. 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.dev resources; the CNCF rbac-police and KubiScan READMEs.
  • Attack paths: KubeHound (Datadog) docs and the "Kubernetes attack graph" talks.
  • Practice: KubernetesGoat "RBAC least privilege", kctf, the "Kubernetes Lan Party" challenges.

Generated from real-life-security-guide-v1.md · 8 Oct 2026 16:36