kube-system's Default SA Bound to cluster-admin
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-bindingkubectl 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.