Kubernetes RBAC Explained (2026): Roles, Bindings and Why get Can Exec
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.
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
Kyverno vs Gatekeeper vs ValidatingAdmissionPolicy in 2026
Kubernetes policy engines compared for 2026: Kyverno 1.19 (CNCF graduated, ClusterPolicy deprecated for CEL), OPA Gatekeeper 3.23 with CEL and generated VAPs, built-in ValidatingAdmissionPolicy (GA 1.30) and MutatingAdmissionPolicy (stable 1.36), latency numbers, and when to use which.
13 minOct 20, 2026How to Enforce Pod Security Standards with Kyverno 1.19 (2026)
Step-by-step: enforce Kubernetes Pod Security Standards with Pod Security Admission namespace labels and Kyverno 1.19 CEL policies. Helm install, audit then enforce, policy reports, mutate and generate policies, PolicyExceptions, and fixes for Kyverno webhook failures.
13 minOct 5, 2026Policy as Code for AI Agents in 2026: Cedar at the Gateway, Rego in the Hook, and the 11.2% the Model Still Lets Through
What actually enforces limits on AI agents in 2026: OPA 1.21 and Rego v1 after the founders moved to Apple, Cedar in Amazon Bedrock AgentCore Policy (GA 3 March 2026, 13 regions, default deny), Kyverno 1.19 retiring ClusterPolicy for CEL, Claude Code hooks that deny even in bypass mode, Codex's Seatbelt and bwrap sandboxes, the MCP 2026-07-28 authorization spec, and why none of it is optional while browser-agent injection success sits at 11.2% and OpenAI says the problem is unlikely to ever be fully solved. With a tested Rego policy and a Cedar policy for tool calls.
13 min