DEV Community

kozhevniko
kozhevniko

Posted on

Reading Your Own Kubernetes RBAC: A Least-Privilege Audit That Finds Real Grants

Reading Your Own Kubernetes RBAC: A Least-Privilege Audit That Finds Real Grants

Kubernetes RBAC is one of the few access-control systems that ships with a complete, machine-readable description of every permission in the cluster. The cluster will tell you exactly who can do what, and it will do so without any additional tooling. Most clusters still have subjects that can read every secret in every namespace, and most of those grants were never deliberately made.

The reason is structural. RBAC grants accumulate. A debugging session produces a temporary ClusterRoleBinding that nobody removes. A default service account gets a role because a workload failed without it. A Helm chart ships a role that is broader than the application needs, and the chart is upgraded rather than reviewed.

Start with what the cluster already knows

The API server exposes RBAC objects through the standard API. The useful starting points are ClusterRoleBinding, RoleBinding, ClusterRole and Role. A binding that references the cluster-admin ClusterRole is the highest-value finding, because it grants every verb on every resource in every namespace.

A first pass should answer four questions:

  1. Which subjects are bound to cluster-admin?
  2. Which subjects can read Secrets cluster-wide?
  3. Which subjects can create or modify workload objects, and therefore run code as any service account in the namespace?
  4. Which bindings reference the system:authenticated or system:unauthenticated groups?

The fourth question matters because a binding to system:authenticated applies to every authenticated identity in the cluster, including any token that leaks.

The permission that is usually underestimated

The ability to create a Pod is effectively the ability to obtain any service account token in that namespace. A subject that can create a Pod can mount a service account token, run code inside the cluster, and use whatever permissions that service account holds. In a namespace where a workload runs with a privileged service account, create pods is a privilege-escalation path.

The same applies to several adjacent verbs. Creating a Deployment, a DaemonSet, a StatefulSet or a CronJob all result in Pods. Creating a Job does too. Any of these is a workload-creation path, and a least-privilege review that treats create pods as low risk while treating get secrets as high risk has the risk ordering backwards.

Aggregated ClusterRoles hide the real grant

Kubernetes ships a set of default ClusterRoles, several of which are built by aggregation. The view, edit and admin roles are assembled from labels: any ClusterRole carrying the matching aggregation label is automatically included. This means the effective permission set of edit is not fixed. It depends on which other ClusterRoles exist and carry the label.

An audit that reads only the role named in the binding will miss the aggregated permissions. The effective permission set has to be resolved by following the aggregation labels to every contributing role.

Reading bindings to service accounts

A workload's identity is its service account. A RoleBinding to a service account is a grant to every Pod that mounts it. Two patterns are worth flagging:

  • A service account bound to a broad role and mounted by many workloads. The blast radius of a compromise in any one workload is the union of the role's permissions.
  • The default service account in a namespace with a non-trivial role. Workloads that do not specify a service account use default. A grant to default is a grant to every such workload.

Automounting is the related control. Since Kubernetes 1.24, service account token volumes are projected and time-limited, and automountServiceAccountToken can be set to false on the service account or the Pod. Workloads that do not call the API server do not need a token.

A repeatable audit sequence

  1. Inventory bindings and their subjects. Separate human identities from service accounts. The remediation paths differ.
  2. Resolve effective permissions. Follow aggregation labels and record the union, not the named role.
  3. Rank by escalation potential. Workload creation, service account impersonation, and secret read come first.
  4. Check namespace boundaries. A RoleBinding is namespace-scoped; a ClusterRoleBinding is not. A cluster-scoped grant made for a namespace-scoped need is usually a mistake.
  5. Check for wildcards. A rule with resources: ["*"] or verbs: ["*"] is a finding regardless of the subject.
  6. Record the owner and the justification. A grant with no justification is a grant to remove.

What to do with the findings

Remediation is usually not deletion. Removing a binding that a workload depends on causes an outage, and the outage produces a re-grant that is broader than the original. The workable pattern is to replace a broad grant with a narrow one that covers the observed API calls, then remove the broad grant after the narrow one has been in place through a full workload cycle.

The audit therefore needs a usage signal, not just a configuration signal. API server audit logs record the verb, resource and subject for each request. Comparing the audit log against the granted permissions produces the set of grants that are never exercised, which is the set that can be removed with the least risk.

Limits of the approach

RBAC is not the only access path in a cluster. Admission webhooks, aggregated API servers, node access and the cloud provider's own IAM all grant access that RBAC does not describe. A least-privilege review that stops at RBAC has covered the most visible layer, not the whole surface.

The value of the RBAC audit is that it is cheap, it is deterministic, and it produces a concrete list. The cluster already contains the data. The work is reading it and deciding which grants still have a reason to exist.

References

Top comments (0)