Role-Based Access Control is the primary authorization system in Kubernetes, and it is also one of the most quietly misused. The mechanics are simple — Roles and ClusterRoles grant permissions, RoleBindings and ClusterRoleBindings attach them to subjects — but the incentives push everyone toward over-provisioning. A developer hits a "forbidden" error, someone grants a broader role to unblock them, and that grant is never walked back. Multiply that across a few years and you get clusters where the average workload can list secrets across every namespace and half the service accounts are effectively cluster-admin. RBAC done this way does not contain a breach; it amplifies one. A single compromised pod inherits whatever its service account can do, and if that is "read all secrets" or "create pods anywhere," the blast radius is the whole cluster.
How RBAC decisions actually get made
Every request to the Kubernetes API server is evaluated against the union of all roles bound to the requesting subject. RBAC is purely additive — there are no deny rules — so a subject's effective permissions are the sum of every binding that touches it, directly or through a group. That design has an important consequence: you cannot fix an over-broad grant by adding a narrower one. You have to find and remove the broad grant itself. It also means auditing "what can this service account do?" requires resolving every binding that references it or a group it belongs to, which is exactly the kind of graph humans are bad at tracing by hand.
Design roles around verbs, not convenience
The most common RBAC anti-pattern is the wildcard. A role that grants * on * in * is cluster-admin by another name, and roles that grant ["get","list","watch","create","update","patch","delete"] on every resource are only marginally better. Scope each role to the specific resources and verbs a workload needs:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: payments
name: config-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
Bind it in the namespace, to a named service account, not to the default one:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-config-reader
namespace: payments
subjects:
- kind: ServiceAccount
name: payments-api
namespace: payments
roleRef:
kind: Role
name: config-reader
apiGroup: rbac.authorization.k8s.io
Prefer namespaced Role/RoleBinding over ClusterRole/ClusterRoleBinding unless a workload genuinely needs cluster-wide reach. A namespaced binding caps the blast radius at one namespace; a ClusterRoleBinding hands it the whole cluster.
Watch for the permissions that grant more permissions
Some verbs are dangerous not for what they touch directly but for the escalation they enable. Guard these especially:
secretsget/list— reading secrets often means reading credentials to other systems, cloud IAM tokens, and database passwords.createonpods— the ability to create a pod is the ability to mount any service account token and run with its privileges; it is a classic escalation primitive.bind/escalateon roles — these let a subject grant itself permissions it does not already have, defeating the point of RBAC.impersonate— lets a subject act as another user or group entirely.- access to the
nodes/proxyandpods/execsubresources — a path to running commands inside other workloads.
Historically, API server flaws such as CVE-2018-1002105 showed how authorization boundaries can be bypassed entirely, which is all the more reason to keep the granted permissions minimal so a bypass reaches as little as possible.
Prefer aggregation and reuse over bespoke sprawl
Every one-off role you create is another object someone has to review and reason about, and RBAC sprawl is how over-provisioning hides in plain sight. Lean on the built-in ClusterRoles Kubernetes ships — view, edit, and admin — for the common cases, and use aggregated ClusterRoles to compose permissions from labeled building blocks rather than copy-pasting rules. Just be aware of what the built-ins actually grant: the default edit role can read Secrets in its namespace, which is often more than a deploy pipeline needs. When a built-in is too broad, define a narrower custom role instead of binding edit and hoping nobody notices. The goal is a small, legible set of roles whose names describe real job functions, not a directory of near-duplicate definitions accreted over years.
Turn off the token you are not using
Every pod, by default, mounts a service account token — a bearer credential for the API server. A workload that never calls the API does not need it, and an unused token in a filesystem is a credential waiting to be stolen. Disable it explicitly:
apiVersion: v1
kind: ServiceAccount
metadata:
name: batch-worker
namespace: payments
automountServiceAccountToken: false
Audit checklist
- No
ClusterRoleBindingtocluster-adminoutside a tiny, named break-glass set - No wildcard (
*) verbs, resources, or apiGroups in application roles secretsaccess granted only to workloads that provably need it- Every binding targets a named service account, never
default automountServiceAccountToken: falseon workloads that do not call the APIbind,escalate, andimpersonateverbs restricted to platform controllers- Bindings reviewed on a schedule and on offboarding, not just at creation
How Safeguard helps
RBAC drift is an infrastructure-as-code problem before it is a runtime problem — the over-broad role was almost always merged in a manifest or Helm chart. Safeguard's IaC scanning reads your RBAC definitions in the pull request and flags wildcard grants, cluster-admin bindings, and dangerous verb combinations before they ever reach the cluster, so least privilege is enforced at review time rather than discovered during an incident. Combined with container security scanning that gates the images your service accounts are attached to, and Griffin AI prioritizing which findings are actually exploitable, you get a coherent picture of both what a workload can do and how likely it is to be compromised in the first place. See the full platform on our comparison page or check pricing for team plans.
Least privilege only holds if something checks it on every change. Start free with Safeguard to scan your Kubernetes RBAC, or read the docs to get set up.