Kubernetes RBAC Guide: Roles, ClusterRoles, and Least Privilege
Master Kubernetes RBAC with Role vs ClusterRole distinctions, ServiceAccount binding, audit policy configuration, kubectl auth can-i, and common RBAC misconfigurations.
Kubernetes RBAC Guide: Roles, ClusterRoles, and Least Privilege
Kubernetes Role-Based Access Control (RBAC) is the primary mechanism for controlling who can do what in a cluster. Misconfigured RBAC is consistently in the top findings of Kubernetes security assessments — often granting cluster-admin to service accounts that need far less, or leaving overly permissive bindings from initial setup. This guide covers RBAC from first principles to production-ready configuration.
Core Concepts
Kubernetes RBAC has four resource types:
| Resource | Scope | Purpose |
|---|---|---|
Role | Namespace | Grants access to resources within one namespace |
ClusterRole | Cluster-wide | Grants access to cluster-wide resources or resources in all namespaces |
RoleBinding | Namespace | Binds a Role or ClusterRole to a subject within a namespace |
ClusterRoleBinding | Cluster-wide | Binds a ClusterRole to a subject across all namespaces |
The most important rule: use the narrowest scope possible. Use Role + RoleBinding before reaching for ClusterRole + ClusterRoleBinding.
Role: Namespace-Scoped Permissions
# role-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""] # "" = core API group (pods, services, configmaps)
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
kubectl apply -f role-pod-reader.yaml
Verbs in RBAC map to HTTP methods and operations:
get— read a specific resource by namelist— list all resourceswatch— stream changescreate— create new resourcesupdate— modify existing resourcespatch— partially modify resourcesdelete— delete resourcesdeletecollection— delete all matching resources
ClusterRole: Cluster-Scoped Permissions
Use ClusterRole for:
- Cluster-scoped resources: nodes, persistent volumes, namespaces
- Access that must span all namespaces
- Reusable roles that get bound per-namespace via
RoleBinding
# clusterrole-node-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
verbs: ["get", "list"]
RoleBinding and ClusterRoleBinding
Bind a Role to a user, group, or ServiceAccount:
# rolebinding-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: monitoring-agent
namespace: production
- kind: User
name: jane@example.com # In OIDC-integrated clusters
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
You can bind a ClusterRole with a RoleBinding to restrict its scope to one namespace:
# Use ClusterRole but limit scope to one namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-manager
namespace: staging
subjects:
- kind: ServiceAccount
name: deploy-bot
namespace: staging
roleRef:
kind: ClusterRole # ClusterRole, not Role
name: edit # Built-in "edit" ClusterRole
apiGroup: rbac.authorization.k8s.io
ServiceAccount Least Privilege
Every Pod runs with a ServiceAccount. By default, it uses the default ServiceAccount in its namespace. This account, by default, has no RBAC permissions — but many guides tell developers to bind cluster-admin to it, which is a critical mistake.
Best practices for ServiceAccounts:
# 1. Create a dedicated ServiceAccount per application
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-service
namespace: production
automountServiceAccountToken: false # Disable unless the app needs the API
---
# 2. Create a minimal Role for the application
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: payment-service-role
namespace: production
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["payment-api-key"] # Only this specific secret
verbs: ["get"]
---
# 3. Bind the Role to the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payment-service-binding
namespace: production
subjects:
- kind: ServiceAccount
name: payment-service
namespace: production
roleRef:
kind: Role
name: payment-service-role
apiGroup: rbac.authorization.k8s.io
Set the ServiceAccount in the Pod spec:
spec:
serviceAccountName: payment-service
automountServiceAccountToken: false
kubectl auth can-i — Auditing Permissions
Verify what a user or service account can do before and after configuration changes:
# Can the current user create deployments in production?
kubectl auth can-i create deployments -n production
# Can a specific service account list secrets?
kubectl auth can-i list secrets -n production \
--as=system:serviceaccount:production:payment-service
# List all actions the monitoring-agent can take in production
kubectl auth can-i --list -n production \
--as=system:serviceaccount:production:monitoring-agent
# Can anyone in the group "developers" delete pods?
kubectl auth can-i delete pods -n production \
--as-group=developers --as=fake-user
Audit Policy
Enable Kubernetes audit logging to track all API calls:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
# Log all secret access at RequestResponse level
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
# Log authentication failures
- level: Metadata
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["configmaps"]
namespaces: ["production", "staging"]
# Log cluster-admin actions
- level: RequestResponse
users: ["cluster-admin"]
# Default: log metadata for everything else
- level: Metadata
Enable in the API server:
# kube-apiserver flags
--audit-log-path=/var/log/kubernetes/audit.log
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100
Common RBAC Misconfigurations
1. Wildcard Verbs and Resources
# BAD — grants everything
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# Also bad — grants everything in core API group
rules:
- apiGroups: [""]
resources: ["*"]
verbs: ["*"]
2. Cluster-Admin Bindings to Service Accounts
# Find all cluster-admin bindings
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects'
Every cluster-admin binding to a ServiceAccount is a potential privilege escalation path if that pod is compromised.
3. Wildcard on Secrets
# BAD — can read ALL secrets in the namespace
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]
# Good — can only read specific secrets
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["my-app-secret", "my-app-tls"]
verbs: ["get"]
4. Bind-Escalate Privilege
If a user can create RoleBindings or ClusterRoleBindings, they can grant themselves any permission that exists in any Role — effectively escalating to cluster-admin:
# Dangerous — allows privilege escalation
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "clusterrolebindings"]
verbs: ["create", "update", "patch"]
Restrict RoleBinding creation to cluster admins only.
Built-in ClusterRoles
Kubernetes ships with several useful built-in ClusterRoles:
| ClusterRole | Description |
|---|---|
view | Read-only access to most resources |
edit | Read/write access, no RBAC or network policy changes |
admin | Full namespace access, can manage RBAC within namespace |
cluster-admin | Full cluster access — use only when absolutely necessary |
Use edit or view as your starting point and grant additional permissions as needed.
RBAC Audit Tools
# rbac-tool — visualize and audit RBAC
kubectl rbac-tool who-can create pods
kubectl rbac-tool show --kind ServiceAccount --name default -n production
# rakkess — show access matrix
kubectl-rakkess --sa production:payment-service
# kube-bench — CIS benchmark including RBAC checks
kube-bench run --targets node,master
Summary Checklist
- Use
Role+RoleBindinginstead ofClusterRole+ClusterRoleBindingwherever possible - No wildcard
*on verbs, resources, or API groups in application roles -
cluster-adminonly bound to human admin users, never ServiceAccounts - Dedicated ServiceAccount per application with
automountServiceAccountToken: false - Secret access scoped to specific
resourceNames - No application ServiceAccount can create RoleBindings
- Audit policy enabled and logs shipped to SIEM
- Regular audit with
kubectl auth can-i --listper ServiceAccount - Use
vieworeditbuilt-in roles as a ceiling, not a floor