RBAC Misconfigurations That Break Production

Quick answer
Most Kubernetes security incidents aren't zero-days. They're RBAC misconfigurations that went unreviewed for months. Here are the patterns that cause the most production damage — and how to find them before they bite you.
- Misconfiguration 1: ClusterRoleBinding to cluster-admin for Automation
- Misconfiguration 2: Secrets Read Access for Application Service Accounts
- Misconfiguration 3: Wildcard on Core API Group
- Misconfiguration 4: Permissive Default Service Account
- Misconfiguration 5: RBAC Allows Privilege Escalation via Pod Creation
12 min read · Security
Kubernetes RBAC misconfigurations rarely announce themselves. They accumulate silently — a wildcard verb here, a ClusterRoleBinding that was "temporary," a service account that somehow ended up with cluster-admin — and surface only when something goes wrong. By then, the damage is done.
This post covers the misconfigurations that cause the most production incidents: data exposure, privilege escalation, accidental deletion, and lateral movement. For each, the failure mode, a real-world scenario, how to detect it, and how to fix it.
Misconfiguration 1: ClusterRoleBinding to cluster-admin for Automation
The Failure Mode
A CI/CD pipeline, an operator, or a monitoring agent is given cluster-admin because it was easier than figuring out the right permissions. When that token is compromised — via a leaked kubeconfig, a supply chain attack, or a misconfigured secret — the attacker has full control of the cluster.
Real-World Scenario
A GitHub Actions workflow uses a service account token stored as a repository secret. The token has cluster-admin. A developer accidentally commits a workflow file that logs environment variables to debugging output. The token is now in the public CI logs. Within hours, an automated scanner finds it, and the cluster's etcd is exfiltrated.
How to Detect
# Find all ClusterRoleBindings to cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name == "cluster-admin") |
{name: .metadata.name, subjects: .subjects}'Any result that isn't a small set of known platform engineers and break-glass accounts is a finding. Service accounts should never appear here.
# Find service accounts with cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name == "cluster-admin") |
.subjects[] | select(.kind == "ServiceAccount")'Fix
Remove the binding. Replace it with a scoped Role covering only what the automation actually needs. See Kubernetes RBAC in Practice for how to size the replacement role.
If the token has been in use, rotate it: delete and recreate the service account (which invalidates all existing tokens), then update the consuming system.
Misconfiguration 2: Secrets Read Access for Application Service Accounts
The Failure Mode
An application service account has get/list on secrets cluster-wide (via a ClusterRole) or even namespace-wide. An attacker who compromises the application pod can enumerate all secrets in the cluster — including database credentials, API keys, certificate private keys, and other applications' secrets.
list on secrets is particularly dangerous: it allows bulk enumeration of secret names and values. get on a specific secret is more defensible but still over-permissive if the application only needs one or two secrets.
Real-World Scenario
A web application is given read access to secrets in its namespace so it can load database credentials at runtime. The Role uses resources: ["*"] instead of resources: ["configmaps", "secrets"] to cover future needs. The application is later compromised via a dependency CVE. The attacker uses the service account token to list all secrets in the namespace, finds the admin password for the internal PostgreSQL instance, and pivots to the database.
How to Detect
1# Find roles that grant secrets read across any namespace
2kubectl get roles,clusterroles -A -o json | \
3 jq '.items[] | select(
4 .rules[]? |
5 (.resources | any(. == "secrets" or . == "*")) and
6 (.verbs | any(. == "list" or . == "*"))
7 ) | .metadata | {name, namespace}'For any result, check whether the application actually needs to list secrets — most don't. Applications that load secrets at startup via environment variable injection (using the valueFrom.secretKeyRef pod spec field) don't need any secrets RBAC at all. The kubelet handles the injection; the application pod never calls the Kubernetes API.
Fix
If the application loads secrets via pod spec injection (the right approach for most apps), remove secrets read RBAC entirely.
If the application genuinely needs to read specific secrets at runtime (dynamic credential rotation, for example), scope with resourceNames:
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames: ["my-app-db-credentials", "my-app-api-key"]resourceNames is one of the most underused RBAC features. It restricts a rule to specific named resources, preventing enumeration.
Misconfiguration 3: Wildcard on Core API Group
The Failure Mode
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]This grants full CRUD on all core API resources: pods, secrets, configmaps, serviceaccounts, persistentvolumes, nodes, namespaces, and more. It's often created as a "just get things working" shortcut and never tightened.
Combined with the ability to create or modify ServiceAccount objects, this becomes a privilege escalation vector: an attacker can create a new service account, bind a ClusterRole to it, and use the new service account to escalate privileges.
Real-World Scenario
A Helm operator is given wildcard core API access so it can deploy any application without RBAC changes. An attacker compromises the operator pod. They use the service account to create a new service account in kube-system, bind cluster-admin to it via a ClusterRoleBinding, and extract a token from the new service account. The privilege escalation takes under 60 seconds.
How to Detect
1# Find any role with wildcard resource or verb on core API group
2kubectl get roles,clusterroles -A -o json | \
3 jq '[.items[] | select(
4 .rules[]? |
5 ((.resources | any(. == "*")) or (.verbs | any(. == "*"))) and
6 (.apiGroups | any(. == "" or . == "*"))
7 ) | {name: .metadata.name, namespace: .metadata.namespace}]'Also check for the specific escalation risk: any subject that can create or bind roles:
kubectl auth can-i create clusterrolebindings \
--as=system:serviceaccount:default:my-operatorFix
Enumerate the specific resources the operator needs. If it manages deployments, services, and configmaps, list exactly those. Never use resources: ["*"] in production.
Server & SSH Hardening Checklist
The firewall, SSH, fail2ban, and update baseline every internet-facing Linux box should pass. Plain Markdown you can run down in an afternoon.
Free. Instant download. You'll also get the occasional deep-dive from the newsletter — unsubscribe anytime.
Misconfiguration 4: Permissive Default Service Account
The Failure Mode
The default service account in a namespace is bound to a role (intentionally or via a copy-paste error). Every pod that doesn't specify serviceAccountName inherits those permissions. This means a compromised application pod — even one that has no business touching the Kubernetes API — has cluster access.
In clusters where automountServiceAccountToken is not explicitly set to false, every pod gets a mounted service account token regardless of whether it needs one. An attacker who executes arbitrary code in any pod can read the token from /var/run/secrets/kubernetes.io/serviceaccount/token and use it.
How to Detect
1# Check what the default service account can do in each namespace
2for ns in $(kubectl get namespaces -o jsonpath='{.items[*].metadata.name}'); do
3 perms=$(kubectl auth can-i --list \
4 --as=system:serviceaccount:${ns}:default \
5 -n ${ns} 2>/dev/null | grep -v "^Resources\|^*\.\*\|no access")
6 if [ -n "$perms" ]; then
7 echo "=== Namespace: ${ns} ==="
8 echo "$perms"
9 fi
10done# Find all bindings to the default service account
kubectl get rolebindings,clusterrolebindings -A -o json | \
jq '.items[] | select(
.subjects[]? |
.kind == "ServiceAccount" and .name == "default"
) | {name: .metadata.name, namespace: .metadata.namespace, role: .roleRef.name}'Fix
Remove any bindings to the default service account. If something breaks, something was depending on it — find what and give it a dedicated service account with explicit permissions.
Disable automatic token mounting cluster-wide where possible:
1# In the namespace's default service account
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5 name: default
6 namespace: production
7automountServiceAccountToken: falseThis doesn't prevent pods from mounting tokens — they can still opt in with automountServiceAccountToken: true in the pod spec — but it removes the default injection for pods that don't need it.
Misconfiguration 5: RBAC Allows Privilege Escalation via Pod Creation
The Failure Mode
A service account that can create pods can escalate to cluster-admin if it can create pods in any namespace that already has privileged service accounts — particularly kube-system.
The attack path:
- Create a pod in
kube-systemwithserviceAccountName: kube-system-sa(any highly-privileged SA) - Exec into the pod
- Read the mounted token from
/var/run/secrets/kubernetes.io/serviceaccount/token - Use that token for privileged operations
Or more directly: create a pod that mounts a host path, gaining access to the underlying node's filesystem.
Real-World Scenario
A developer has create pods access in the kube-system namespace as a debugging convenience ("they just need to run a debug container"). They create a pod that mounts /etc/kubernetes from the host. The pod reads the node's kubeconfig, which has control plane access. The developer now has unintended cluster-admin equivalent access.
How to Detect
# Who can create pods in kube-system?
kubectl who-can create pods -n kube-system
# Who can create pods cluster-wide?
kubectl who-can create pods --all-namespacesAny human user or service account that can create pods in kube-system is a high-severity finding.
# Verify Pod Security Admission labels on sensitive namespaces
kubectl get ns kube-system -o jsonpath='{.metadata.labels}' | jq .If pod-security.kubernetes.io/enforce is not set to restricted or baseline on sensitive namespaces, pod creation can be used to run privileged containers.
Fix
Remove pod creation rights from non-platform accounts in kube-system and other privileged namespaces. If developers need debugging access, use ephemeral debug containers via kubectl debug (which doesn't require pod create RBAC in the target namespace) or dedicated debug namespaces with restricted pod security.
Enable Pod Security Admission on sensitive namespaces:
kubectl label namespace kube-system \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/warn=restrictedMisconfiguration 6: Stale Bindings from Deleted Service Accounts
The Failure Mode
A service account is deleted — because the application was decommissioned, the team restructured, or a cleanup pass was done — but its RoleBindings and ClusterRoleBindings are not. The bindings now point to a non-existent subject.
This is a hygiene issue, not an immediate security risk. But the bindings can be accidentally "reactivated" if a new service account is created with the same name. The new service account inherits all the old bindings without anyone realising it.
How to Detect
1# Find bindings pointing to non-existent service accounts
2kubectl get rolebindings,clusterrolebindings -A -o json | jq -r '
3 .items[] |
4 . as $binding |
5 .subjects[]? |
6 select(.kind == "ServiceAccount") |
7 "\($binding.metadata.namespace)/\($binding.metadata.name): \(.namespace)/\(.name)"
8' | while IFS=: read binding sa; do
9 ns=$(echo "$sa" | cut -d/ -f1)
10 name=$(echo "$sa" | cut -d/ -f2 | xargs)
11 if ! kubectl get serviceaccount "$name" -n "$ns" &>/dev/null 2>&1; then
12 echo "STALE: $binding -> $sa"
13 fi
14doneFix
Delete stale bindings as part of decommission runbooks. Any time a service account is deleted, the deletion runbook should include:
kubectl delete rolebinding -n <ns> <binding-name>
# or
kubectl delete clusterrolebinding <binding-name>For clusters with many teams, this is easiest to enforce via GitOps — if the service account manifest is removed from Git, the bindings for it should be removed in the same PR.
Building a Detection Habit
These misconfigurations don't require a dedicated security tool to find — kubectl and jq cover most of them. The gap is usually that no one runs the checks regularly.
Automate the basics:
- Weekly audit job — a CronJob that runs
kubectl who-can create pods -n kube-systemand similar checks, sends output to Slack if non-empty - Admission control — Kyverno or OPA/Gatekeeper policies that block creation of ClusterRoleBindings to
cluster-adminfor service accounts - GitOps drift detection — if RBAC is stored in Git and managed by Argo CD, Argo's sync drift detection alerts when bindings exist that aren't in the repo
- Quarterly RBAC review — half-day exercise: run
rakkessfor every service account in production, review anything with write permissions
The RBAC that breaks production is almost never a single dramatic misconfiguration. It's six months of accumulated "just for now" decisions that nobody revisited.
Frequently Asked Questions
How do I know if my RBAC has already been exploited?
Check Kubernetes audit logs for API calls from service accounts that shouldn't be making them. Specifically, look for:
list secretscalls from application service accountscreatecalls onclusterrolebindingsorrolebindingsfrom non-platform accountsexecorportforwardcalls from CI service accounts
# If using CloudWatch (EKS) — filter audit logs
aws logs filter-log-events \
--log-group-name /aws/eks/<cluster>/cluster \
--filter-pattern '{ $.objectRef.resource = "secrets" && $.verb = "list" }'Is there a Kubernetes-native way to enforce RBAC hygiene?
Kyverno can enforce RBAC policies at admission time — blocking creation of overly permissive roles before they're applied. Example: a Kyverno ClusterPolicy that fails any ClusterRole containing verbs: ["*"] with resources: ["*"]. This is the most effective prevention layer because it blocks the misconfiguration at the point of creation.
Should I use view, edit, and admin built-in ClusterRoles?
For human users bound within a namespace (via RoleBinding, not ClusterRoleBinding), yes — they're well-calibrated. For service accounts, no — they're too broad. A service account that needs to read ConfigMaps doesn't need the full view ClusterRole (which includes read on pods, services, and 30+ other resource types; note: view does NOT grant access to Secrets since K8s 1.22 — use edit or a custom role for that).
For the RBAC design patterns that prevent these issues, see Kubernetes RBAC in Practice: Least Privilege Without the Headache. For the broader supply chain security context, see Supply Chain Security Tools for Kubernetes. For the broader security hardening checklist that includes RBAC, PSA, and NetworkPolicy layers, see Kubernetes Security Hardening Guide. For scoping an AI agent's cluster credentials so it can never hold one of the write verbs above, see Give an AI Agent Read-Only Access to Kubernetes.
Found a RBAC misconfiguration in your cluster and not sure how to clean it up safely? Talk to us at Coding Protocols — we help platform teams remediate access control issues without breaking running workloads.
Official References
- RBAC authorization — Role, ClusterRole and binding semantics
- Configure service accounts — token projection and the default service account
Was this article helpful?
Be the first to rate this article
Related Topics
Found this useful? Share it.


