Skip to main content
KBY Technologies Logo
cd ../config-traps
risk/register/kube-systems-default-sa-bound-to-cluster-admin.html
Kubernetes RBACcritical severityKubernetes

kube-system's Default SA Bound to cluster-admin

Overview

A cluster-admin ClusterRoleBinding attached to the default service account in kube-system means every pod landing there without an explicit serviceAccountName inherits a live, unscoped API token. RBAC reviews scoped to named accounts miss it entirely, and one compromised kube-system pod becomes full cluster takeover.

At a glance

Unsafe setting
A ClusterRoleBinding grants the cluster-admin ClusterRole to system:serviceaccount:kube-system:default.
Failure trigger
Any kube-system pod without an explicit serviceAccountName inherits the default SA's cluster-admin token via automount.
Blast radius
A single compromised kube-system pod obtains cluster-admin API access, enabling secret theft, privileged pod creation, and RBAC persistence cluster-wide.
Recommended control
Delete the offending ClusterRoleBinding, disable automount on the default SA, and enforce named ServiceAccounts with scoped RoleBindings via a Gatekeeper constraint.

Fix commands and configuration

kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name=="default" and .subjects[]?.namespace=="kube-system")'
kubectl delete clusterrolebinding permissive-binding
kubectl patch serviceaccount default -n kube-system -p '{"automountServiceAccountToken": false}'

The Trap

A ClusterRoleBinding — commonly named permissive-binding or copied from an old Kubernetes Dashboard install guide — binds the cluster-admin ClusterRole to the subject system:serviceaccount:kube-system:default. This exact command still circulates in bootstrap scripts, Helm post-install hooks, and internal wiki pages: kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --serviceaccount=kube-system:default.

The Default State

Engineers add this binding to unblock a stuck dashboard, metrics-server, or CI runner during initial cluster setup, then never remove it. Because the target is the default service account rather than a named one, it doesn’t show up when access reviews grep for specific ServiceAccount names. Combine this with automountServiceAccountToken left at its cluster-wide default of true, and any pod scheduled into kube-system without an explicit serviceAccountName field silently mounts a cluster-admin-capable token at /var/run/secrets/kubernetes.io/serviceaccount.

The Blast Radius

DaemonSets, log shippers, node-exporter sidecars, and misconfigured operator pods routinely land in kube-system and inherit this SA by omission, not by design. A single container escape or dependency CVE inside any of those pods now has an authenticated, cluster-admin bearer token: it can read every Secret in every namespace, create privileged pods on arbitrary nodes, rewrite RBAC to plant persistence, and exfiltrate node kubelet credentials. OPA/Gatekeeper policies written against workload PodSecurity context rarely inspect ClusterRoleBinding subjects, so this path survives PodSecurity admission entirely and only surfaces in a full RBAC subject audit.

The Lead Mechanic Fix

Run kubectl get clusterrolebindings -o json | jq '.items[] | select(.subjects[]?.name=="default" and .subjects[]?.namespace=="kube-system")' to enumerate offending bindings, then kubectl delete clusterrolebinding permissive-binding. Patch the default SA with kubectl patch serviceaccount default -n kube-system -p '{"automountServiceAccountToken": false}'. Require every workload to declare a named, minimally scoped ServiceAccount bound via RoleBinding rather than ClusterRoleBinding, and add a Gatekeeper ConstraintTemplate that denies any ClusterRoleBinding whose subject kind is ServiceAccount with name default. Validate ongoing drift with rbac-lookup 'system:serviceaccount:kube-system:default' in CI.