Kubernetes RBAC Explained (2026): Roles, Bindings and Why get Can Exec

Oct 31, 2026 · 13 min · Ajay Kumar

Kubernetes RBAC is four object types and a list of verbs, which is exactly why it gets less attention than it deserves. In January 2026 a researcher showed that a permission requested by 69 public Helm charts, get on nodes/proxy, is enough to run commands in every pod on a node, and the Kubernetes security team closed the report as working as intended. After fourteen years of granting and revoking cloud and cluster access, that reads to me like a summary of how RBAC goes wrong: the YAML says read-only and the API disagrees. This post explains Kubernetes RBAC as it works in v1.37: roles and bindings, service accounts, aggregation, the permissions that quietly escalate, what changed in 1.35 and 1.36, how I would scope CI and AI agents, and how to audit it. If you are staring at a forbidden error right now, the companion guide on fixing Kubernetes RBAC forbidden errors is the quicker read.

How does Kubernetes RBAC work?

RBAC is an authorizer: by the time it runs, the API server has authenticated the request and holds a username and a list of groups. RBAC graduated to stable in Kubernetes 1.8 in 2017, and the rbac.authorization.k8s.io/v1 API has kept its shape ever since.

There are four objects. A Role holds rules inside one namespace; a ClusterRole holds rules not tied to a namespace. A RoleBinding grants a role's rules to subjects inside one namespace; a ClusterRoleBinding grants a ClusterRole's rules across the cluster.

Subjects are User, Group or ServiceAccount. Users and groups are not cluster objects at all, just strings from whatever authenticated the request: a certificate's CN and O fields, OIDC claims, an EKS access entry, a Google account. Service accounts are real objects, seen by RBAC as system:serviceaccount:<namespace>:<name>.

Each rule lists API groups, resources (including subresources such as pods/log), verbs and optionally resourceNames. The RBAC reference is clear on the property that matters most: permissions are purely additive and there are no deny rules. If any binding allows an action, it is allowed.

Here is a deliberately small deploy identity:

# deployer-rbac.yaml: deploy rights for CI in one namespace.
# rbac.authorization.k8s.io/v1, the same API from Kubernetes 1.8 to 1.37.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: payments
automountServiceAccountToken: false     # CI mints short-lived tokens instead
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer
  namespace: payments
rules:
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "patch"]   # roll images; no create or delete
- apiGroups: [""]                            # "" is the core API group
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]            # read rollout status and logs
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer
  namespace: payments
subjects:
- kind: ServiceAccount
  name: ci-deployer
  namespace: payments
roleRef:                                     # immutable once created
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: deployer

Even this Role is bigger than it looks. Patching a Deployment rewrites its pod template, which is workload creation by another name, and the RBAC good practices page warns that anyone who can create workloads in a namespace can mount its Secrets and run as any of its service accounts.

Role vs ClusterRole, RoleBinding vs ClusterRoleBinding

The role says where rules are defined; the binding says where they apply. Three combinations are valid. Role plus RoleBinding is the plain namespaced case. ClusterRole plus RoleBinding is the reuse pattern: define view once, bind it per namespace, and the grant still stops at that namespace. ClusterRole plus ClusterRoleBinding applies everywhere, including future namespaces, and is the only way to grant cluster-scoped resources such as nodes, namespaces, PersistentVolumes and CRDs.

A ClusterRoleBinding cannot point at a Role, and roleRef is immutable: to change the role you delete and recreate the binding, or let kubectl auth reconcile do it. The docs explain why. It lets you delegate managing a binding's subjects without letting anyone swap the role underneath.

Subjects (User, Group, ServiceAccount) get rules through a binding. Rules only add; there is no deny. Role + RoleBinding Rules defined in one namespace Grants them in that namespace only Cannot reach cluster-scoped objects Typical: an app's own service account, or a CI deployer roleRef.kind: Role ClusterRole + RoleBinding Rules defined once for the cluster Grants them only in the binding's namespace Typical: view, edit or admin for one team in one namespace roleRef.kind: ClusterRole ClusterRole + ClusterRoleBinding Grants them in every namespace, including future ones, plus nodes, namespaces, PVs, CRDs, /healthz Typical: controllers and the few humans who run the cluster The only roleRef.kind it accepts Not allowed: a ClusterRoleBinding that points at a Role (validation rejects roleRef.kind: Role). roleRef is immutable. To change the role, delete and recreate the binding, or use kubectl auth reconcile.
The three valid ways to combine roles and bindings, and the one the API rejects. Behaviour as documented in the Kubernetes v1.37 RBAC reference and enforced by the RBAC validation code.

What can cluster-admin, admin, edit and view actually do?

Every cluster ships four user-facing ClusterRoles, and managed providers ship look-alikes. The catches are in the fine print.

Role Source Reads Secrets Edits RBAC The catch
cluster-admin Kubernetes default Yes Yes Even in a RoleBinding it controls the namespace, Namespace object included
admin Kubernetes default Yes In its namespace Cannot write ResourceQuota or the Namespace
edit Kubernetes default Yes No Runs pods as any service account in the namespace
view Kubernetes default No No Still reads ConfigMaps and pod logs
AmazonEKSAdminViewPolicy EKS access policy Yes No Reads everything, Secrets included
AmazonEKSEditPolicy EKS access policy Yes No Includes impersonate on service accounts
roles/container.developer GKE IAM Yes Read only Granted in IAM, invisible to a RoleBinding grep

Above all of these sits system:masters. Membership bypasses every RBAC check and cannot be revoked by deleting bindings, because there is no binding to delete. kubeadm now gives admin.conf the revocable group kubeadm:cluster-admins and keeps the real system:masters certificate in a separate super-admin.conf for emergencies. On EKS clusters still on the deprecated aws-auth ConfigMap, the cluster creator holds system:masters without appearing in any visible configuration.

How ClusterRole aggregation works

admin, edit and view are aggregated. A ClusterRole with an aggregationRule names a label selector, and a control-plane controller fills its rules from every ClusterRole carrying that label. That is how CRDs extend the defaults: an operator ships a ClusterRole labelled rbac.authorization.k8s.io/aggregate-to-view: "true", and everyone bound to view can read its resources. So installing a chart can widen view or edit for every team at once, and kubectl get clusterroles -l rbac.authorization.k8s.io/aggregate-to-edit=true belongs in any operator review. The docs also warn against setting rules on an aggregated ClusterRole, even to an empty list: under server-side apply, GitOps controllers that force conflicts will keep clearing rules the control plane keeps refilling.

Which RBAC permissions are dangerous?

The dangerous permissions are rarely cluster-admin; they are the ones that look narrow. This list follows the Kubernetes project's own privilege escalation risks.

Permission What it really grants Source
get, list or watch on secrets Full Secret contents, service account tokens included Good practices
create pods or workloads Mount any Secret or volume in the namespace, run as any of its service accounts Good practices
create persistentvolumes hostPath, so the node's filesystem Good practices
get on nodes/proxy The kubelet API, including exec in every pod on the node, bypassing audit and admission Kubelet authz
escalate on roles Write roles with permissions you do not hold RBAC docs
bind on roles Bind roles you do not hold, including to yourself RBAC docs
impersonate Act as that user, group or service account for anything it can do Good practices
create on serviceaccounts/token Mint tokens for existing service accounts Good practices
CSR create plus approval Client certificates with any name, system components included Good practices
Write on webhook configurations Read, or mutate, every admitted object Good practices
patch on namespaces Relabel to loosen Pod Security or NetworkPolicy selectors Good practices

Two smaller traps sit nearby. Wildcards ("*") cover resource types that do not exist yet, so a wildcard role grows with every CRD you install. And resourceNames cannot restrict top-level create, so "may only create the pod named X" is not expressible.

The escalation checks themselves are sound: to create a Role or bind one, you must already hold its permissions. escalate and bind are the deliberate holes. In the EKS access policy list, the AWS Backup restore policy names both explicitly, which is fine for a restore tool and a red flag anywhere else.

Why is get not always read-only?

A WebSocket connection opens with an HTTP GET, and Kubernetes maps HTTP verbs to RBAC verbs, so a "read" can open an exec session. This bit Kubernetes twice.

At the API server, the fix shipped in v1.35. The changelog says pods/exec, pods/attach and pods/portforward now require create for both SPDY and WebSocket requests, where WebSocket requests previously "only required get permission". It sits behind the AuthorizePodWebsocketUpgradeCreatePermission gate, beta and on by default, so tools relying on the old behaviour start getting forbidden after the upgrade.

At the kubelet, the answer was different. Graham Helton disclosed on 26 January 2026 that a service account with only get on nodes/proxy can reach the kubelet on port 10250 and run commands in any pod on that node. He reported it on 1 November 2025, tested v1.34 and v1.35, and was told "Won't Fix (Working as intended)". Exec through the kubelet does not produce a pods/exec audit event. Horizon3's follow-up names Prometheus, Grafana, Datadog and Elastic among the charts that request the permission.

Kubernetes v1.36 shipped the mitigation the project pointed to: fine-grained kubelet authorization went GA on 24 April 2026. Metrics, stats and logs already had their own subresources; the kubelet now also checks nodes/pods, nodes/healthz and nodes/configz before falling back to nodes/proxy. The announcement calls nodes/proxy "effectively a node-level superuser capability", but exec, attach and port-forward still have no subresource of their own. The fix is on your side:

# monitoring-agent ClusterRole for Kubernetes v1.36+ (KubeletFineGrainedAuthz is GA).
# Replaces the common rule: resources: ["nodes/proxy"], verbs: ["get"]
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-agent
rules:
- apiGroups: [""]
  resources: ["nodes/metrics", "nodes/stats"]   # scrape /metrics and /stats only
  verbs: ["get"]
- apiGroups: [""]
  resources: ["nodes"]                           # discover targets
  verbs: ["get", "list", "watch"]

Then check whether your chart's values let you drop nodes/proxy, block new grants of it with admission policy, and restrict who can reach port 10250.

Service accounts: the identity most RBAC is about

Default RBAC grants service accounts outside kube-system nothing beyond API discovery. Any pod without serviceAccountName runs as its namespace's default account, so whatever you grant default you grant every such pod. Give each workload its own account.

Tokens have improved. Since v1.22 pods get a short-lived, rotated token through a projected volume, and since v1.24 Kubernetes no longer creates a permanent token Secret per service account. The legacy token cleaner, stable since v1.30, invalidates auto-generated Secret tokens unused for a year. Outside a pod, kubectl create token <name> --duration 15m uses the TokenRequest API.

Set automountServiceAccountToken: false wherever a pod never calls the API. And because workload creation reaches every service account in a namespace, the docs advise that boundaries within a namespace "should be considered weak". Separate trust levels with namespaces, not roles.

RBAC for CI pipelines and AI agents

A CI pipeline should hold something like the deployer Role above and nothing cluster-wide. Better still, it should hold no stored Kubernetes token: authenticate to the cloud with the CI provider's OIDC token and map that cloud role to a Kubernetes group through EKS access entries or GKE IAM. On self-managed clusters, the structured authentication configuration, stable since v1.34, lets the API server trust external OIDC issuers directly. With pull-based GitOps, the controller becomes the most privileged identity in the cluster; guard its service account accordingly.

AI agents are the same problem with a less predictable caller: a non-human identity whose verbs are chosen by a model, which is why I treat agent identity as its own problem. Unit 42's OperTraitors research, published 29 September 2026, found slightly over 5% of assessed operators requesting excessive privileges, including IBM's Prometurbo with cluster-wide read on Secrets (CVE-2026-6389, CVSS 8.8).

Good scoping exists in public too. AWS's AmazonAIOpsAssistantPolicy, added in August 2025 for CloudWatch investigations, is get and list on pods, logs, events, workloads and a few others, with no Secrets, no watch and no exec. It does include ConfigMaps and pods/log, the honest cost of letting an investigator see anything. The Kubernetes MCP server supports read_only = true, and its setup guide binds a dedicated service account to view with a two-hour token.

For agents acting on behalf of a person, watch constrained impersonation. It went beta and on by default in v1.36 and splits impersonation into two grants: impersonate:user-info on a named user, and per-action verbs such as impersonate-on:user-info:list on specific resources. An agent can then act as Jane only to list pods in one namespace, instead of inheriting everything Jane can do, and the audit event records the constraint used.

How to audit Kubernetes RBAC: kubectl auth can-i, rbac-tool and kubectl-who-can

Start with kubectl. kubectl auth whoami (stable since v1.28) shows who the API server thinks you are, and kubectl auth can-i --list -n payments --as=system:serviceaccount:payments:ci-deployer shows what an identity can do. The API reference warns that such listings "may be incomplete depending on the server's authorization mode", and AWS documents that can-i --list omits EKS access-policy permissions.

The open-source tools are older than the problems they find. rbac-tool last released v1.20.0 in October 2024 and does who-can, lookup, policy-rules and analysis. kubectl-who-can is at v0.4.0 from February 2022 and rakkess (kubectl access-matrix) at v0.5.1 from November 2022. Because the API has not changed, they still work, but none can see EKS access policies, GKE IAM or AKS Azure RBAC.

When I want an answer I can diff in Git, I use kubectl and jq:

#!/usr/bin/env bash
# rbac-risk.sh: who has cluster-admin, and which roles grant risky rules.
# Checked with jq 1.7.1; needs read access to RBAC objects.
set -euo pipefail

echo "== Subjects bound to cluster-admin"
kubectl get clusterrolebindings -o json | jq -r '
  .items[] | select(.roleRef.name == "cluster-admin")
  | .metadata.name as $b | (.subjects // [])[]
  | "\($b)\t\(.kind)\t\(.namespace // "-")\t\(.name)"'

echo "== Non-system roles with escalate, bind, impersonate, wildcards, nodes/proxy or Secret reads"
kubectl get clusterroles,roles -A -o json | jq -r '
  .items[] | . as $r
  | select($r.metadata.name | startswith("system:") | not)
  | (.rules // [])[]
  | select(
      (.verbs | index("escalate") or index("bind") or index("impersonate") or index("*"))
      or ((.resources // []) | index("nodes/proxy") or index("*"))
      or (((.resources // []) | index("secrets")) and (.verbs | index("get") or index("list") or index("watch")))
    )
  | "\($r.kind)\t\($r.metadata.namespace // "-")\t\($r.metadata.name)\t\(.verbs | join(","))\t\((.resources // .nonResourceURLs // []) | join(","))"' \
  | sort -u

Auditing shows what exists; stopping new risky grants is admission control's job, compared in Kyverno vs Gatekeeper vs ValidatingAdmissionPolicy. My Kubernetes security roundup covers which bindings attackers create first.

FAQ

What is the difference between a Role and a ClusterRole in Kubernetes?

A Role defines permissions inside one namespace. A ClusterRole defines them without a namespace, so it can cover cluster-scoped resources such as nodes and be reused across namespaces. The binding decides where they apply: a RoleBinding limits them to one namespace, a ClusterRoleBinding applies them everywhere.

Can Kubernetes RBAC deny a permission?

No. RBAC rules are purely additive. To stop an action some binding allows, remove or narrow the binding, or reject the resulting object with an admission policy such as ValidatingAdmissionPolicy, Kyverno or Gatekeeper.

Does the view ClusterRole allow reading Secrets?

No. view excludes Secrets because they expose service account tokens, but it does read ConfigMaps and pod logs. edit can read Secrets and run pods as any service account in the namespace, so treat it as close to admin there.

Is get on nodes/proxy safe for a monitoring agent?

No. The Kubernetes docs state that get on nodes/proxy is not read-only, because kubelet exec over WebSocket is authorized as get. On v1.36 and later, grant nodes/metrics and nodes/stats instead and drop nodes/proxy wherever the chart allows.

This quarter I would run the jq audit on every cluster and delete each cluster-admin binding nobody can explain; move monitoring service accounts off nodes/proxy; upgrade anything below v1.35 so WebSocket exec needs create, and see which tools break; give every CI job and agent its own service account, a namespaced binding and a token measured in minutes; and on EKS, finish moving from aws-auth to access entries. None of this is new; agents just make the bill for skipping it arrive sooner.

Related: Kubernetes Security in 2026: First Attack in 18 Minutes, and What Stops It, Kyverno vs Gatekeeper vs ValidatingAdmissionPolicy in 2026 and Workload Identity Federation Explained (2026): OIDC Instead of Cloud Keys.

I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.

Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.


Related