Kubernetes RBAC in 2026: Roles, Bindings and Least Privilege That Actually Holds
TL;DR: Kubernetes RBAC fails in practice not because the model is weak but because grants accrete: a wildcard here, a cluster-admin binding there, a "temporary" service account that becomes permanent. The model itself is simple — subjects get verbs on resources in a scope, grants are additive only, and nothing is denied, only not granted. Least privilege that holds means: one service account per workload with token automount off by default, namespace-scoped Roles as the default unit, groups (not individuals) as human subjects, secrets treated as the crown jewels they are, and a regular audit of who can do what — verified with
kubectl auth can-i, not assumed.
Originally published on DevOps Enginer.
Why RBAC is where cluster security is won or lost
The security-basics guide in this series covered the checklist; this guide goes deep on the one control that decides what a compromised credential can actually do. Most cluster incidents follow the same shape: an attacker (or a fat-fingered script) obtains a token — from a pod, a CI job, a leaked kubeconfig — and the blast radius is whatever that token may do. Network policies limit where it can talk; RBAC limits what it can be. A read-only token in one namespace is an embarrassment. A cluster-admin token is a breach report.
And RBAC sprawl is the default outcome, because every grant is a small act of unblocking someone: the deploy pipeline "needs" broader rights, the dashboard "needs" cluster-wide secrets, the new tool "needs" admin "for setup." Each is reasonable on the day. Together they are the incident, pre-positioned.
The model, precisely
Four ideas carry the whole system:
- Subjects — who: a User (external identity, typically from a certificate or OIDC token), a Group, or a ServiceAccount (in-cluster identity).
-
Rules — what: verbs (
get,list,watch,create,update,patch,delete, and a few specials) on resources (pods,deployments,secrets…) within API groups (""for core,apps,batch,rbac.authorization.k8s.io…). - Scope — where: a Role grants within one namespace; a ClusterRole grants cluster-wide or for cluster-scoped resources (nodes, persistent volumes, namespaces themselves).
- Bindings — the connection: a RoleBinding attaches a Role or ClusterRole to subjects inside one namespace (a ClusterRole bound by a RoleBinding is effectively trimmed to that namespace); a ClusterRoleBinding attaches a ClusterRole everywhere.
Two properties shape everything else:
- Additive only. There are no deny rules. A subject's rights are the union of all its bindings. "Remove the bad grant" always means deleting or narrowing a binding — and "who granted what" questions are answered by enumerating bindings, never by finding an exception.
- Default deny. With RBAC as the authorisation mode, a subject with no grants can do nothing. Every capability is an explicit decision — which is why the accretion pattern above is a process failure, not a model failure.
A worked example: read-only log access for support
The support team needs to read pods and logs in one production namespace — nothing else, nowhere else:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: shop-prod
name: log-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: shop-prod
name: log-reader-support
subjects:
- kind: Group
name: support-engineers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: log-reader
apiGroup: rbac.authorization.k8s.io
Details doing quiet work here: the subject is a Group (people join and leave groups; bindings should not name individuals), pods/log is a subresource that must be granted explicitly, and there is no secrets, no create, no second namespace. Verify it the way an auditor would:
kubectl auth can-i get pods --namespace shop-prod --as jane --as-group support-engineers # yes
kubectl auth can-i get secrets --namespace shop-prod --as jane --as-group support-engineers # no
kubectl auth can-i list pods --all-namespaces --as jane --as-group support-engineers # no
kubectl auth can-i with --as/--as-group impersonation is the ground-truth test for any identity — build the habit of checking grants with it instead of reading YAML and reasoning hopefully.
Service accounts: the identities that actually get stolen
Humans are rarely the compromised identity; workloads are. Service-account discipline is therefore the highest-value RBAC work:
-
One service account per workload. The
defaultservice account in each namespace should have no grants. A dedicated account per deployment makes grants reviewable and revocation surgical. - Turn off token automount unless needed. Most applications never call the Kubernetes API. For them the token is pure attack surface:
apiVersion: v1
kind: ServiceAccount
metadata:
name: shop-api
namespace: shop-prod
automountServiceAccountToken: false
(The same field exists per-pod in the pod spec, which wins when both are set.) Where a token is needed, modern clusters issue short-lived projected (bound) tokens rather than long-lived secrets — prefer them, and treat any long-lived service-account token secret as a credential to inventory.
- Deployer accounts are prime targets. A CI service account that can create deployments in production can, by design, run arbitrary images with that namespace's service accounts — near-admin inside its scope. Narrow its verbs to exactly the resources the pipeline applies, keep it namespace-scoped, and protect its credential like a production key (the OIDC guide's keyless pattern applies to how the pipeline authenticates to the cloud; the in-cluster account deserves the same suspicion).
ClusterRoles: handle with both hands
Cluster-wide grants are sometimes right — an observability agent reading node and pod metadata everywhere, an operator managing its resource type across namespaces. The disciplines:
-
Prefer a trimmed custom role over the built-ins' extremes. The default
cluster-adminis total;admin,edit, andvieware namespace-oriented defaults with specific shapes (notably,viewexcludes secrets — a deliberate, instructive choice). For cluster-wide read, write the ClusterRole that lists exactly the resources the tool reads; "cluster-reader" patterns from the community are a starting point, not a destination. -
Aggregation composes, so audit the aggregate. A ClusterRole with an
aggregationRulecollects rules from labelled ClusterRoles (this is how the built-in admin/edit/view roles grow with CRDs). Powerful for platforms, dangerous when a stray label quietly widens a widely bound role — include aggregated roles in audits. - ClusterRoleBindings deserve a list, kept short. Every entry is a cluster-wide statement. Review the full list on a schedule; in a healthy cluster it fits on one screen and every line has an owner who can explain it.
The dangerous verbs and resources
Audits should hunt specific shapes, because damage concentrates:
-
Wildcards —
verbs: ["*"]orresources: ["*"]in anything but a deliberately tiny set of admin roles. A wildcard silently includes tomorrow's new subresources. -
get/listonsecrets— especially cluster-wide. Reading secrets is effectively holding every credential in scope (the ConfigMaps and Secrets guide covers why). Treat any new secrets grant as a security review, not a config change. -
createonpods— pod creation with a chosen service account and mounts is a well-trodden path to privilege escalation; so arecreateon rolebindings/clusterrolebindings and the specialbindandescalateverbs, which exist precisely to gate grant-making itself.impersonateis equally sensitive: it is the other identity, for whoever holds it. -
deletecollectionand broaddelete— the availability side of the same coin.
Human access: groups, short-lived credentials, no shared kubeconfigs
- Map your identity provider's groups to Group subjects once, and manage membership in the IdP — not by editing bindings per person. Joiner/leaver processes then work through the system people already trust for access changes.
- Issue short-lived credentials: exec-plugin/OIDC flows from your cloud or IdP, so a copied kubeconfig expires instead of opening a permanent door. A long-lived client certificate cannot be revoked (Kubernetes has no certificate revocation for this) — another reason to prefer token-based, expiring auth.
- Break-glass, deliberately. Keep an emergency admin path, alarm on its use, and test it — an incident is the wrong time to discover nobody can grant the rights to fix it. Emergency access is a designed feature; routine access through it is the failure.
Auditing: make "who can do what" a query, not a debate
-
kubectl auth can-i --list --as <identity>prints an identity's effective rights in a namespace — the fastest spot check. - Enumerate bindings as code review: RoleBindings and ClusterRoleBindings are YAML in your GitOps repo (the Argo CD guide's pattern) — changes to them should be pull requests with security-aware reviewers, not
kubectlimprovisation. - Tooling in the
kubectl-who-can/ RBAC-lookup family answers the reverse question ("who can get secrets in prod?") across the cluster; run it in CI or on a schedule and diff the output — new entries are events worth a human glance. - API-server audit logs record who did what (the logging guide covers collecting them); RBAC denials in those logs are a signal of both attacks and of teams fighting their own permissions — both worth knowing.
Common mistakes
- Binding cluster-admin "temporarily." Temporary grants are permanent until the audit that never happens. Time-box with a ticket, an owner, and a removal date — or better, grant the specific role the task needs.
- One service account for everything in the namespace. Every pod inherits the union of rights; one compromised container spends them all.
- Secrets access for convenience. Dashboards and debug tools asking for cluster-wide secrets are asking for the keys to everything. Scope to the namespace, or redesign the tool's access.
- Confusing RoleBinding-to-ClusterRole scope. Binding a ClusterRole via a RoleBinding grants it only in that namespace — teams regularly believe they granted (or limited) something else. Read the binding kind, not just the role kind.
- Stale bindings. People change teams; projects end; vendor tools are uninstalled and their service accounts and bindings remain, valid and forgotten. The audit diff is how they surface.
-
Testing with your own admin account. It works for you, so permissions "work." Test as the workload's identity (
--as=system:serviceaccount:ns:nameor by running the pod) before the 3 a.m. discovery.
Where this fits in the series
RBAC is the identity layer the rest of the series assumes: the security-basics guide sets the posture, the network policies guide constrains where identities can reach, the ConfigMaps and Secrets guide protects what grants can expose, and the observability and logging guides watch it all — including the audit stream that records every grant being used. Get RBAC boring, and the other controls get to do their jobs.
FAQ
Can RBAC deny a specific action?
No. RBAC is additive — a subject's permissions are the union of its grants, and there is no deny rule to carve exceptions. If a grant is too broad, narrow the role or split it; for true deny semantics (blocking an action even for the granted), admission-time policy tools are the layer that does it.
Role or ClusterRole for a cross-namespace tool?
If it genuinely acts in many namespaces, a ClusterRole — bound cluster-wide only if it must act everywhere, or via RoleBindings in selected namespaces when its reach should be enumerated namespace by namespace. The second pattern keeps the grant list explicit and reviewable.
Should each pod have its own service account?
Each workload identity should: one per deployment/workload is the practical unit, shared by its replicas. Per-pod accounts multiply objects without adding a trust boundary; per-namespace sharing erases the boundary that exists.
How do I see everything a service account can do?
kubectl auth can-i --list --as=system:serviceaccount:<namespace>:<name> -n <namespace> for a namespace view, repeated per namespace or answered cluster-wide with RBAC lookup tooling. Make it a habit after every binding change.
Are the built-in admin/edit/view roles safe to use?
They are reasonable, maintained defaults — with shapes you should know: view excludes secrets and most write access, edit adds writes but not role management, admin adds role management within the namespace but not namespace deletion or quota changes. The risk is rarely the built-ins; it is the custom wildcard beside them.
Top comments (0)