Most teams donβt need Kubernetes cluster-admin access β they need least-privilege roles aligned with actual job functions.
π Table of Contents
- π Core Concepts β Understanding the Mechanism
- π§ Role Design β Applying Least-Privilege
- π§ͺ Example: Read-Only Namespace Viewer
- β οΈ Gotcha: Subresources and Verbs
- π ClusterRoles β When You Need Global Scope
- π Reusing Built-in ClusterRoles
- π Auditing and Troubleshooting β Who Can Do What?
- π Debugging "Forbidden" Errors
- π Tip: Avoid Default Namespace Pitfalls
- π© Final Thoughts
- β Frequently Asked Questions
- What's the difference between Role and ClusterRole?
- How do I revoke access for a user?
- Can I use RBAC to restrict access to specific pods by label?
- π References & Further Reading
π Core Concepts β Understanding the Mechanism
Kubernetes RBAC is enforced at the API server level using attribute-based request evaluation. Every kubectl command or direct API call is parsed into four attributes: user , verb , resource , and namespace. The authorization layer checks whether any RoleBinding or ClusterRoleBinding grants the requested access. The API server evaluates each request independently β no session state is retained. When a user runs kubectl get pods, the flow is: 1. Authentication via client certificate, bearer token, or OIDC.
2. Authorization through the RBAC engine.
3. A lookup for RoleBinding (or ClusterRoleBinding) in the target namespace linking the user to a Role allowing get on pods. RBAC separates policy definition (Role) from assignment (RoleBinding). Crucially, Roles are namespaced , while ClusterRoles apply cluster-wide. Hereβs a minimal Role granting read-only access to Pods and Services in the production namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: namespace: production name: pod-reader
rules:
- apiGroups: [""] resources: ["pods", "services"] verbs: ["get", "list", "watch"]
This defines the allowed operations but does not grant access until bound. To grant it to alice@example.com, apply this RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: name: alice-pod-reader namespace: production
subjects:
- kind: User name: alice@example.com apiGroup: rbac.authorization.k8s.io
roleRef: kind: Role name: pod-reader apiGroup: rbac.authorization.k8s.io
Then run:
$ kubectl apply -f role.yaml
role.rbac.authorization.k8s.io/pod-reader created
rolebinding.rbac.authorization.k8s.io/alice-pod-reader created
Now verify access:
$ kubectl get pods -n production -as alice@example.com
NAME READY STATUS RESTARTS AGE
api-7689b7b8d5-2xklp 1/1 Running 0 23m
worker-5c67b8d9f-9zq2m 1/1 Running 0 22m
Access fails outside the namespace:
$ kubectl get pods -n staging -as alice@example.com
Error from server (Forbidden): pods is forbidden: User "alice@example.com" cannot list resource "pods" in API group "" in the namespace "staging"
The API server denies by default β no match means no access. Thereβs no implicit inheritance or wildcard escalation.
βPermissions should be a whitelist, not a handout.β
π§ Role Design β Applying Least-Privilege
The right RBAC policy grants just enough access β nothing more. Start by identifying:
- Which resources are needed (e.g.,
deployments,pods) - Which verbs are required (
get,create,patch) - In which namespace(s) Avoid wildcards like
*inverbsorresources. Instead, explicitly list required operations. For example, a CI/CD pipeline deploying tostagingdoesnβt need fullcluster-admin. It only requires: -
get,update,patchondeployments -
create,deleteonpods(for job runners) -
getonsecrets(for image pulls) So define a targetedRole:kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata: namespace: staging name: ci-deployer
rules:- apiGroups: ["apps"] resources: ["deployments"] verbs: ["get", "update", "patch"]
- apiGroups: [""] resources: ["pods"] verbs: ["create", "delete"]
- apiGroups: [""] resources: ["secrets"] verbs: ["get"]
Bind it to the service account used by GitHub Actions:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata: name: ci-role-binding namespace: staging
subjects:
- kind: ServiceAccount name: github-actions namespace: ci
roleRef: kind: Role name: ci-deployer apiGroup: rbac.authorization.k8s.io
This reflects a core pattern: roles should map to functional responsibilities , not individual users.
π§ͺ Example: Read-Only Namespace Viewer
For developers who need to debug workloads but not modify them, create a read-only role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: namespace: dev-team-alpha name: dev-reader
rules:
- apiGroups: [""] resources: ["pods", "services", "configmaps", "secrets"] verbs: ["get", "list", "watch"]
- apiGroups: ["apps"] resources: ["deployments", "replicasets"] verbs: ["get", "list", "watch"]
Bind it to the entire team using a group:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: name: team-dev-reader namespace: dev-team-alpha
subjects:
- kind: Group name: dev-team-alpha@company.com apiGroup: rbac.authorization.k8s.io
roleRef: kind: Role name: dev-reader apiGroup: rbac.authorization.k8s.io
Members can now use kubectl logs, describe, and get β but cannot exec into containers or delete resources.
β οΈ Gotcha: Subresources and Verbs
Some operations require access to subresources, which are not covered by top-level resource rules. For instance, kubectl logs accesses the pods/log subresource. If the role only allows get on pods, the logs call fails:
$ kubectl logs api-7689b7b8d5-2xklp -n dev-team-alpha -as dev-user
Error from server (Forbidden): pods "api-7689b7b8d5-2xklp" is forbidden: User "dev-user" cannot get resource "pods/log" in API group "" in the namespace "dev-team-alpha"
Fix it by explicitly including the subresource:
- apiGroups: [""] resources: ["pods", "pods/log"] verbs: ["get", "list", "watch"]
Subresources must be specified by name β thereβs no wildcard expansion.
π ClusterRoles β When You Need Global Scope
A Role is scoped to a single namespace. For cross-cutting concerns like monitoring or backup, use ClusterRole. A ClusterRole defines cluster-wide permissions. But a ClusterRole alone grants no access β it must be bound via ClusterRoleBinding (cluster-wide effect) or RoleBinding (namespaced binding, but referencing a global role). For Prometheus, which needs metrics from all nodes and pods, define:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata: name: prometheus-scraper
rules:
- apiGroups: [""] resources: ["nodes", "nodes/metrics", "services", "endpoints"] verbs: ["get", "list", "watch"]
- apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list", "watch"]
Then bind it to the service account:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata: name: prometheus-scraper-binding
subjects:
- kind: ServiceAccount name: prometheus namespace: monitoring
roleRef: kind: ClusterRole name: prometheus-scraper apiGroup: rbac.authorization.k8s.io
Now the scraper can collect metrics from all nodes and pods. But do not use ClusterRoles for developers. Most user workloads should stay namespaced.
π Reusing Built-in ClusterRoles
Kubernetes provides built-in ClusterRoles: view, edit, admin. - view: read-only access within a namespace
-
edit: read/write to most resources (excludes role creation) -
admin: full access to all resources in a namespace, including roles (but not namespace deletion) You can bind them directly:$ kubectl create rolebinding bob-viewer -clusterrole=view -user=bob@example.com -namespace=production
rolebinding.rbac.authorization.k8s.io/bob-viewer created
But prefer custom roles. Built-ins are broad and may change across Kubernetes versions β making them unsuitable for production least-privilege policies.
π Auditing and Troubleshooting β Who Can Do What?
Even well-designed RBAC setups require validation and debugging. Kubernetes provides kubectl auth can-i and audit logs for this. Check a userβs access inline:
$ kubectl auth can-i get pods -as alice@example.com -namespace production
yes
$ kubectl auth can-i delete nodes -as alice@example.com
no
This invokes the same authorization logic as live API requests. For policy inspection, use kubectl describe on bindings:
$ kubectl describe rolebinding ci-role-binding -n staging
Name: ci-role-binding
Labels:
Annotations:
Role: Kind: Role Name: ci-deployer
Subjects: Kind: ServiceAccount Name: github-actions Namespace: ci
This shows exactly who gets what role and where. For long-term compliance, enable Kubernetes audit logging. Each API request logs:
-
user,group,sourceIP -
verb,resource,subresource -
responseStatusQuery logs for sensitive operations likecreate podsorget secretsto detect misuse.
π Debugging "Forbidden" Errors
When a user receives Forbidden, follow these steps:
1. Confirm the RoleBinding exists in the correct namespace.
2. Check that roleRef references the correct Role or ClusterRole.
3. Verify the subjects list includes the correct user, group, or service account.
4. Use kubectl auth can-i to simulate the request. Remember: RBAC denies by default. No matching rule means no access β no exceptions.
π Tip: Avoid Default Namespace Pitfalls
kubectl defaults to the default namespace unless overridden. If the binding is in production, omitting -n results in failure:
$ kubectl get pods -as alice@example.com
Error from server (Forbidden): pods is forbidden: ...
But with namespace:
$ kubectl get pods -n production -as alice@example.com
NAME READY STATUS RESTARTS AGE
api-7689b7b8d5-2xklp 1/1 Running 0 23m
Always specify -n when testing or scripting.
π© Final Thoughts
RBAC is more than a security control β itβs an operational safeguard. When implemented correctly, it prevents accidental deletions, limits lateral movement during breaches, and clarifies ownership boundaries. The real value of kubernetes rbac roles lies in predictability. Systems where identities have only the permissions they need are easier to debug, safer to deploy, and simpler to audit. Start with small, functional roles: one for CI, one for developers, one for monitoring. Validate access using kubectl auth can-i. Iterate based on actual needs. And when asked for cluster-admin, respond: βNo β what specific actions do you need?β That shift β from blanket trust to explicit need β is how Kubernetes scales securely.
β Frequently Asked Questions
What's the difference between Role and ClusterRole?
A Role is namespaced and applies only within a single namespace. A ClusterRole is cluster-scoped and can grant access to cluster-wide resources like nodes or persistent volumes. ClusterRoles can be bound using ClusterRoleBinding (cluster-wide) or RoleBinding (namespaced binding). (Also read: βοΈ Mastering gcp vpc peering setup tutorial made easy)
How do I revoke access for a user?
Delete the corresponding RoleBinding or ClusterRoleBinding. Access is revoked immediately because Kubernetes re-evaluates permissions on every API request. No reload or restart is required.
Can I use RBAC to restrict access to specific pods by label?
No. Kubernetes RBAC does not support attribute-based access control such as βonly pods with label env=prodβ. It operates at the resource type and namespace level. For label-level restrictions, use external policy controllers like OPA Gatekeeper.
π References & Further Reading
- Official Kubernetes RBAC documentation β complete reference for roles, bindings, and evaluation logic: kubernetes.io
- Kubernetes API access control guide β covers authentication, authorization, and admission control layers: kubernetes.io
- Best practices for RBAC in production β from the Kubernetes hardening guide: kubernetes.io
Top comments (0)