Skip to main content
RunBook Academy

KubernetesLVIII · RBACRBAC

RBAC anti-patterns — what to avoid and why

Advanced⏱ ~15 minkubectl

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

Not yet marked complete on this device.

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

  1. 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.
  2. 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.
  3. 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-i and 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

  1. Q1. What is the danger of `verbs: ["*"]` in a Role, even on a seemingly read-only resource?

  2. Q2. A RoleBinding to the `default` ServiceAccount in a namespace only affects Pods that explicitly request the `default` SA; other Pods are not affected.

  3. 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.

  4. 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.