Auditing Kubernetes RBAC: Finding (and Fixing) Your ClusterRole Wildcards

RBAC misconfigurations rarely break anything on the day they’re introduced. Someone’s debugging a permissions error, grants a ServiceAccount cluster-admin to unblock themselves, means to scope it down later, and later never comes. Six months on, that overly broad binding is still there, unnoticed, and it’s exactly the kind of thing that turns a compromised CI pipeline or a single leaked token into a full cluster takeover instead of a contained incident. Cluster assessments regularly turn up at least one ServiceAccount bound to a ClusterRole with verbs: ["*"] or `resources: [“*”]“ — this isn’t a rare finding, it’s closer to the default state of an unaudited cluster.
Why this matters more than it seems like it should
RBAC is the layer that determines what happens after something goes wrong elsewhere — a compromised dependency, a leaked CI token, a misconfigured webhook. If every ServiceAccount has broad permissions, any one of those becomes a cluster-wide incident. If RBAC is properly scoped, the same compromise is contained to whatever that specific ServiceAccount actually needed access to. New clusters face exploitation attempts within minutes of being reachable — RBAC scope is frequently the only thing standing between “an attacker found one weak point” and “an attacker owns the cluster.”
Step 1: Find every ClusterRoleBinding and what it’s bound to
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name + " -> " + (.subjects[]?.name // "none")'
This surfaces every binding to cluster-admin specifically — the highest-privilege built-in role. In a healthy cluster, this list should be short and every entry should be explainable: a human admin account, maybe a GitOps controller that genuinely needs broad access. If it includes ServiceAccounts for individual application workloads, that’s your first finding.
Step 2: Find wildcard verbs and resources in custom roles
Built-in roles are one problem; custom ones with wildcards baked in are the more common one, because they’re usually created under time pressure to unblock something specific and never revisited:
kubectl get clusterroles -o json | \
jq -r '.items[] | select(.rules[]?.verbs[]? == "*" or .rules[]?.resources[]? == "*") | .metadata.name'
For each role this returns, pull the actual rule and ask: does this ServiceAccount genuinely need every verb against every resource, or does it need get/list/watch against three specific resource types? The gap between those two is usually enormous, and closing it is almost always just a matter of writing out what the workload actually calls against the Kubernetes API — which for most application workloads is a short, specific list.
Step 3: Cross-reference bindings against what’s actually running
A role with excessive permissions that’s bound to nothing is a lower priority than one that’s actively in use. Check which ServiceAccounts are actually referenced by running pods:
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.spec.serviceAccountName}{"\n"}{end}' | sort -u
Cross-reference this against your list of over-privileged RoleBindings from steps 1 and 2. A wildcard ClusterRole bound to a ServiceAccount with zero running pods is cleanup work; one bound to a ServiceAccount actively used by a public-facing workload is this week’s priority.
Step 4: Replace wildcards with the specific verbs actually used
Once you know what a workload actually does against the API, write the narrowest Role that covers it. A ServiceAccount that only needs to read ConfigMaps and Secrets in its own namespace should get exactly that:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: config-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps", "secrets"]
verbs: ["get", "list", "watch"]
Note this is a Role, not a ClusterRole — scoped to a single namespace unless the workload genuinely needs cross-namespace access, which most don’t. Defaulting to ClusterRole when Role would do is its own, smaller version of the same over-privileging problem.
Step 5: Layer identity provider integration on top, if you haven’t
RBAC authorization only matters if authentication is solid underneath it. Integrating Kubernetes with an OIDC identity provider (Okta, Azure AD, Google Workspace) enforces MFA and centralized deprovisioning at the authentication layer, before RBAC is even evaluated — so a departed employee’s access is revoked in one place instead of hunted down across every ServiceAccount and kubeconfig they touched.
Making this a recurring check, not a one-time cleanup
The uncomfortable truth about RBAC audits is that a clean result today doesn’t stay clean. New services get scaffolded with copy-pasted RBAC from whatever example was closest to hand, and “closest to hand” often means broader than necessary because broad permissions never produce an error, only narrow ones do. This is exactly the kind of drift that a continuous architecture review is meant to catch — the same wildcard check from step 2, rerun automatically as new roles get created, instead of rediscovered during the next scheduled audit six months from now.
Frequently asked questions
How urgent is fixing a wildcard ClusterRole bound to nothing currently running? Lower urgency than one that’s actively used, but still worth fixing — an unused overly-broad binding is a liability the moment something new gets scheduled under that ServiceAccount, possibly by someone who doesn’t know its permissions are that broad.
Does switching from wildcards to specific verbs ever break things?
It can, if the specific-verb list is incomplete — this is why testing in staging first matters. The most common miss is forgetting a watch verb needed for a controller’s reconciliation loop, which shows up as the workload silently failing to react to changes rather than an obvious error.
Is cluster-admin ever appropriate for a ServiceAccount?
Rarely, and almost never for application workloads. Cluster-management tooling (some GitOps controllers, backup tools) can have a legitimate case, but it should be a short, deliberately reviewed list, not a default.
How does this relate to Pod Security Standards? They’re complementary, not redundant — RBAC controls who can do what against the Kubernetes API; Pod Security Standards control what a running pod itself is allowed to do (privileged mode, host access, root). Both matter, and a strong RBAC posture doesn’t compensate for a weak Pod Security Standard, or vice versa.
See RBAC scope alongside health, cost, and architecture checks across your fleet — start free with one cluster.