Safeguard
Container Security

Kubernetes RBAC Security: Least Privilege That Actually Holds

Wildcard verbs, cluster-admin bindings, and forgotten service account tokens turn RBAC into a rubber stamp. Here is how to design roles that contain a breach instead of amplifying it.

Marcus Chen
Cloud Security Engineer
6 min read

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:

  • secrets get/list — reading secrets often means reading credentials to other systems, cloud IAM tokens, and database passwords.
  • create on pods — 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/escalate on 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/proxy and pods/exec subresources — 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 ClusterRoleBinding to cluster-admin outside a tiny, named break-glass set
  • No wildcard (*) verbs, resources, or apiGroups in application roles
  • secrets access granted only to workloads that provably need it
  • Every binding targets a named service account, never default
  • automountServiceAccountToken: false on workloads that do not call the API
  • bind, escalate, and impersonate verbs 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.