RBAC Best Practices
This is a working checklist for keeping Kubernetes access least-privilege, auditable, and easy to reason about.
It targets platform and security engineers who own cluster RBAC across multiple app teams.
How to Use This List
Treat each group as a review area, not a one-time setup.
Walk the groups in order when you stand up a cluster, and revisit them on your quarterly RBAC review.
For each practice, confirm it with a concrete command or manifest rather than assuming it is true.
The recurring theme: grant the fewest verbs on the fewest resources, to groups not individuals, and audit the dangerous edges.
A - Least Privilege by Default
Start from zero and add. Give a new subject no permissions, then grant only the verbs and resources it demonstrably needs.
Prefer namespaced Roles over ClusterRoles. Scope access to one namespace unless the resource is genuinely cluster-scoped, like nodes or CRDs.
Avoid wildcards in application Roles. verbs: ["*"] and resources: ["*"] silently absorb future APIs, so enumerate what you mean.
Reuse built-in roles where they fit. The view, edit, and admin ClusterRoles cover most team needs and are maintained upstream.
Verify every grant with can-i. Run kubectl auth can-i --list --as <subject> after changes to confirm you did not over-grant.
B - No cluster-admin for App Teams
Never bind cluster-admin to app teams. Give a team full control of its own namespace with the admin ClusterRole bound via a namespaced RoleBinding.
Keep cluster-admin to a tiny, named set. Only platform owners and a monitored break-glass identity should hold it.
Watch bindings to broad groups. A ClusterRoleBinding to system:authenticated or system:masters effectively removes RBAC and is a critical finding.
Split platform duties. Separate who can manage RBAC itself from who can deploy workloads, so no single app role can escalate.
C - Bind to Groups, Not Individuals
Bind Roles to OIDC groups. Directory membership then drives access, so onboarding and offboarding need no cluster change.
Match group prefixes exactly. The --oidc-groups-prefix on the API server must equal the prefix in your bindings, or the binding never applies.
Avoid embedding user certificates. X.509 users are hard to revoke and carry no group refresh, so reserve them for bootstrap only.
Document each group's intent. Record what a group is for in version control so its bindings are reviewable.
D - Scope Workload Identity
Give each workload its own ServiceAccount. Never let application pods share the namespace default account.
Leave the default ServiceAccount unbound. Any binding on it leaks access to every pod that did not opt out.
Disable token automounting when unused. Set automountServiceAccountToken: false on pods that never call the API server.
Use short-lived projected tokens. Rely on audience-scoped, expiring tokens and avoid manually created long-lived Secret tokens.
E - Guard the Dangerous Verbs
Track who can exec into prod. create on pods/exec and pods/attach is shell access, so review it every quarter.
Restrict Secret reads. Treat get/list on secrets as credential access and grant it narrowly, ideally with resourceNames.
Audit escalate, bind, and impersonate. These verbs let a subject grant or assume permissions it does not hold, so flag every occurrence.
Monitor break-glass accounts. Alert whenever a high-privilege identity is used, and require a ticket to justify it.
F - Make RBAC Auditable
Manage RBAC as code. Keep Roles and bindings in Git and apply them through GitOps so every change is reviewed and diffable.
Enable API audit logging. Capture who used pods/exec, secrets, and impersonate, then ship events to a SIEM.
Review quarterly. Enumerate high-risk grants on a fixed cadence and remove permissions the audit log shows are unused.
Detect drift. Diff live bindings against the Git source of truth to catch out-of-band changes.
Prune stale bindings. Remove RoleBindings for departed users, deleted namespaces, and retired workloads before they accumulate.
When You Are Done
You should be able to name every holder of cluster-admin from memory or a one-line command.
No application team should hold cluster-wide write access, and no workload should carry a token it does not use.
Every human logs in through SSO, and every binding traces to a group with a documented purpose.
Your audit log records the dangerous verbs, and your quarterly review has a written record of findings and removals.
Above all, RBAC should be boring: every grant is explained by a group, a namespace, and a documented need, with nothing left over.
When someone asks "why can this subject do that," the answer is a single reviewed binding in Git, not a mystery to reverse-engineer.
FAQs
How do app teams manage their namespace without cluster-admin?
Bind the built-in admin ClusterRole to their group with a namespaced RoleBinding. They get full control of that namespace and nothing else.
Why bind to groups instead of users? Group bindings let the identity provider drive access. Adding or removing a person in the directory changes their cluster access with no manifest edit.
What are break-glass accounts? High-privilege identities used only in emergencies. Keep them few, require justification to use, and alert on every use.
Which permissions should I review most often?
pods/exec, secrets, impersonate, escalate, bind, and any binding to a broad group like system:authenticated.
How do I prove least privilege in CI?
Run kubectl auth can-i assertions against your ServiceAccounts in the pipeline and fail the build if a subject can do more than intended.
Should I ever use ClusterRoleBindings? Yes, for genuinely cluster-scoped, read-mostly access like viewing nodes. Avoid them for write access, which almost always belongs in a namespaced RoleBinding.
Related
- How Kubernetes Decides Who Can Do What
- RBAC Basics
- Service Accounts
- OIDC & SSO for kubectl
- RBAC Auditing
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).