Security
12 min readMay 1, 2026Updated August 26, 2026

RBAC Misconfigurations That Break Production

AJ
Ajeet Yadav
Platform & Cloud Engineer
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.

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

bash
# 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.

bash
# 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

bash
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:

yaml
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

yaml
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

bash
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:

bash
kubectl auth can-i create clusterrolebindings \
  --as=system:serviceaccount:default:my-operator

Fix

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

bash
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
bash
# 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:

yaml
1# In the namespace's default service account
2apiVersion: v1
3kind: ServiceAccount
4metadata:
5  name: default
6  namespace: production
7automountServiceAccountToken: false

This 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:

  1. Create a pod in kube-system with serviceAccountName: kube-system-sa (any highly-privileged SA)
  2. Exec into the pod
  3. Read the mounted token from /var/run/secrets/kubernetes.io/serviceaccount/token
  4. 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

bash
# 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-namespaces

Any human user or service account that can create pods in kube-system is a high-severity finding.

bash
# 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:

bash
kubectl label namespace kube-system \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted

Misconfiguration 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

bash
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
14done

Fix

Delete stale bindings as part of decommission runbooks. Any time a service account is deleted, the deletion runbook should include:

bash
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:

  1. Weekly audit job — a CronJob that runs kubectl who-can create pods -n kube-system and similar checks, sends output to Slack if non-empty
  2. Admission control — Kyverno or OPA/Gatekeeper policies that block creation of ClusterRoleBindings to cluster-admin for service accounts
  3. 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
  4. Quarterly RBAC review — half-day exercise: run rakkess for 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 secrets calls from application service accounts
  • create calls on clusterrolebindings or rolebindings from non-platform accounts
  • exec or portforward calls from CI service accounts
bash
# 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

Was this article helpful?

Be the first to rate this article

Related Topics

Kubernetes
RBAC
Security
Platform Engineering
Incident Response
Access Control

Found this useful? Share it.

Practice this

Related tools

Read Next