RBAC anti-patterns — what to avoid and why
What you'll learn
- Identify the ten most common RBAC anti-patterns in production
- Explain why each one is dangerous and the attacker path it enables
- Migrate away from each anti-pattern to a minimum-surface alternative
- Audit a cluster for anti-patterns using `kubectl auth can-i` and inspection
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
RBAC anti-patterns are the recurring misconfigurations that show up in production clusters. Each one has a known attacker path; each one is detectable; each one has a minimum-surface alternative. This lesson catalogues the top ten, explains why each is dangerous, and shows the migration path.
Anti-pattern 1: wildcard verbs
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
A SA with this rule can get, create, update,
patch, delete, deletecollection, and update
Pods. The intent was probably get, list, watch.
The reality is full write access.
Migration: replace verbs: ["*"] with the explicit
verbs the workload needs.
Anti-pattern 2: wildcard resources
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["get", "list", "watch"]
A SA with this rule can read every resource in the core API group: Pods, Services, ConfigMaps, Secrets, Nodes, Events, PersistentVolumes, ServiceAccounts, and more. The intent was probably ConfigMaps and Pods.
Migration: list the explicit resources the workload needs.
Anti-pattern 3: wildcard API groups
rules:
- apiGroups: ["*"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
A SA with this rule can read Deployments in every API
group: apps, apps.k8s.io, extensions, and any
custom API group. The intent was probably just apps.
Migration: list the explicit API groups (["apps"]).
Anti-pattern 4: bind to system:authenticated
subjects:
- kind: Group
name: system:authenticated
apiGroup: rbac.authorization.k8s.io
Every authenticated user gets the role. A developer with
legitimate dev access has prod access.
Migration: bind to a specific IdP group
(idp:prod-admins).
Anti-pattern 5: bind to system:serviceaccounts:<ns>
subjects:
- kind: Group
name: system:serviceaccounts:prod
apiGroup: rbac.authorization.k8s.io
Every SA in prod gets the role, including the
default SA.
Migration: bind to a specific SA by name.
Anti-pattern 6: bind to system:masters
subjects:
- kind: Group
name: system:masters
apiGroup: rbac.authorization.k8s.io
Every user in system:masters (already cluster-admin)
gets the role. The binding is redundant with the
built-in cluster-admin binding.
Migration: remove the binding. The group already has cluster-admin.
Anti-pattern 7: default SA with broad binding
# The default SA in 'prod' is bound to a broad Role
subjects:
- kind: ServiceAccount
name: default
namespace: prod
Every Pod that does not specify a SA uses default.
The binding applies to every such Pod.
Migration: bind default to nothing, and set
automountServiceAccountToken: false on every Pod.
Anti-pattern 8: RoleBinding without a Role
A RoleBinding exists but its roleRef.name is wrong
or the Role is missing. The binding is inert but
clutters the audit. Worse, an operator may assume
the binding grants something it does not.
Migration: remove unused bindings, or fix the roleRef to point at the intended Role.
Anti-pattern 9: ClusterRoleBinding with a Role ref
kind: ClusterRoleBinding
roleRef:
kind: Role
name: pod-reader
The API server rejects this at admission; the binding never comes into effect. If the operator did not notice the rejection, the intent (cluster-wide pod read) is not enforced.
Migration: change kind: Role to
kind: ClusterRole, or change to a RoleBinding.
Anti-pattern 10: allow-all ClusterRole
kind: ClusterRole
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
This is cluster-admin. A SA bound to it can do
anything.
Migration: identify what the SA actually needs, and create a minimum-surface ClusterRole.
flowchart LR
A[Anti-pattern] --> B[Audit]
B --> C[Document minimum surface]
C --> D[Create new Role/ClusterRole]
D --> E[Replace binding]
E --> F[Verify with auth can-i]
Detecting anti-patterns
A script that walks every Role and ClusterRole:
#!/bin/bash
# audit-roles.sh — find RBAC anti-patterns
kubectl get clusterroles -o json | jq -r '
.items[] |
select(
.rules[]?.resources[]? == "*" or
.rules[]?.verbs[]? == "*" or
.rules[]?.apiGroups[]? == "*"
) |
"WILDCARD: \(.metadata.name)"
'
kubectl get roles -A -o json | jq -r '
.items[] |
select(
.rules[]?.resources[]? == "*" or
.rules[]?.verbs[]? == "*" or
.rules[]?.apiGroups[]? == "*"
) |
"WILDCARD: \(.metadata.namespace)/\(.metadata.name)"
'
# Find bindings to built-in dangerous groups
kubectl get rolebindings,clusterrolebindings -A -o json | jq -r '
.items[] |
select(
.subjects[]?.name == "system:authenticated" or
.subjects[]?.name == "system:unauthenticated" or
.subjects[]?.name | startswith("system:serviceaccounts:")
) |
"WIDE-SUBJECT: \(.metadata.namespace // "cluster")/\(.metadata.name) -> \(.subjects[]?.name)"
'
A cluster whose audit reports WILDCARD or
WIDE-SUBJECT has RBAC anti-patterns that must be
remediated.
Production failure modes
- Wildcards are accepted because “it works.” A team ships a chart with wildcard verbs and never revisits. The chart is in production for years. The fix is to audit, document, replace.
- Bindings to built-in groups are not detected. The audit script is not run; the bindings persist. The fix is to run the audit in CI/CD against every change.
- Migration is not tested. The team replaces the
binding, the workload fails because the new Role
does not have what the workload needs. The fix is
to test the new Role with
kubectl auth can-iand to ship the change behind a feature flag.
Cross-course references
- The Observability course covers the audit log that records RBAC decisions and exposes anti-patterns.
- The Linux course covers the file permissions that Kubernetes RBAC does not enforce.
Quiz
Knowledge check · 4 questions
Q1. What is the danger of `verbs: ["*"]` in a Role, even on a seemingly read-only resource?
Q2. A RoleBinding to the `default` ServiceAccount in a namespace only affects Pods that explicitly request the `default` SA; other Pods are not affected.
Q3. Your audit script reports a RoleBinding `cluster-info-reader` in `kube-public` bound to `Group: system:authenticated`. The intent was to allow all OIDC users to read the cluster-info ConfigMap. An ex-employee (whose IdP account was deactivated but not deleted) accesses the cluster using their old token. They read the cluster-info ConfigMap. Why?
The ex-employee's IdP account was deactivated (no new tokens issued) but the cached token from a prior session is still valid. The `cluster-info-reader` binding grants read to `system:authenticated`, which includes any user with a valid token, including the ex-employee's cached token.
Q4. Name three RBAC anti-patterns and the minimum-surface alternative for each.
Passing score: 75%. Answers are checked in this browser.
Production discipline
RBAC anti-patterns are detectable, enumerable, and
fixable. The audit script runs in CI/CD against every
Helm install and every cluster change; the output is
the backlog. Each anti-pattern has a minimum-surface
alternative; each migration is testable with
kubectl auth can-i. A cluster whose RBAC has been
audited and remediated has a defensible posture; a
cluster whose RBAC has not been audited is one
compromised Pod away from an incident. The discipline
is to ship minimum-surface Roles, audit for wildcards,
and treat every binding to a built-in group as a
finding.