Kubernetes RBAC is one of those topics that looks simple in the docs and bites you in production. The concepts are straightforward — but the defaults are permissive, the pitfalls are common, and most teams only discover their gaps after an incident.
This guide covers RBAC from the ground up with real manifests you can use today. Use the Kubernetes RBAC Generator to scaffold bindings for your specific use case.
Core Concepts
RBAC in Kubernetes has four building blocks:
Subjects — who gets permissions. A subject is one of:
- ›
User— a human identity (often from OIDC/LDAP, not created as a K8s object) - ›
Group— a group of users - ›
ServiceAccount— an in-cluster identity for pods and automation
Roles and ClusterRoles — what is allowed. These define a set of rules: which API groups, resources, and verbs are permitted.
RoleBindings and ClusterRoleBindings — the glue between subjects and roles.
The key namespace distinction:
| Object | Scope |
|---|---|
Role | Single namespace |
ClusterRole | Cluster-wide |
RoleBinding | Binds any Role or ClusterRole to subjects within one namespace |
ClusterRoleBinding | Binds a ClusterRole to subjects cluster-wide |
A ClusterRole bound via a RoleBinding only grants permissions within that namespace. This is a useful pattern: define a reusable ClusterRole (e.g., namespace-admin), then bind it per-namespace with RoleBinding.
Verbs and Resources
RBAC rules control access by API group, resource type, and verb:
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
Common verbs:
| Verb | What it allows |
|---|---|
get | Read a single resource by name |
list | List all resources of a type |
watch | Stream changes (used by controllers) |
create | Create new resources |
update | Replace an existing resource |
patch | Partially update a resource |
delete | Delete a resource |
deletecollection | Delete all resources matching a selector |
To find the API group for a resource:
kubectl api-resources --sort-by=name | grep deployment
# NAME SHORTNAMES APIVERSION NAMESPACED KIND
# deployments deploy apps/v1 true Deployment
The APIVERSION column gives you the API group (apps). Core resources like pods, services, and configmaps use "" (empty string) as the API group.
Pattern 1: Read-Only User
A common need: give a developer read-only access to a specific namespace so they can inspect running workloads without being able to change anything.
# ClusterRole that can be reused across namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-readonly
rules:
- apiGroups: ["", "apps", "batch", "autoscaling"]
resources:
- pods
- pods/log
- deployments
- replicasets
- statefulsets
- daemonsets
- jobs
- cronjobs
- horizontalpodautoscalers
- services
- endpoints
- configmaps
- persistentvolumeclaims
- events
verbs: ["get", "list", "watch"]
---
# Bind it to a user in one namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-readonly
namespace: production
subjects:
- kind: User
name: alice@example.com # Must match the identity from your OIDC provider
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-readonly
apiGroup: rbac.authorization.k8s.io
Alice can now run kubectl get pods -n production and kubectl logs but cannot delete, scale, or modify anything.
Pattern 2: CI/CD Service Account
A CI pipeline (GitHub Actions, GitLab CI, ArgoCD) needs a service account with just enough permission to deploy. Scope it to the namespace it deploys into.
apiVersion: v1
kind: ServiceAccount
metadata:
name: github-actions
namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deploy-role
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps", "secrets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: github-actions-deploy
namespace: production
subjects:
- kind: ServiceAccount
name: github-actions
namespace: production
roleRef:
kind: Role
name: deploy-role
apiGroup: rbac.authorization.k8s.io
Generate a token for this service account and base64-encode it for your CI secret:
kubectl create token github-actions -n production --duration=87600h
This service account cannot read from other namespaces, cannot modify RBAC, and cannot touch cluster-scoped resources.
Pattern 3: Namespace Admin
A team that owns a namespace needs full control within it — but nothing outside it.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-admin
namespace: team-alpha
subjects:
- kind: Group
name: team-alpha # From your OIDC provider's group claims
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin # Built-in ClusterRole — full namespace access
apiGroup: rbac.authorization.k8s.io
Kubernetes ships with four built-in ClusterRoles worth knowing:
| Built-in Role | What it grants |
|---|---|
cluster-admin | Full cluster access, including RBAC modification |
admin | Full namespace access (most resources, including RBAC within namespace) |
edit | Read/write on most namespace resources, no RBAC |
view | Read-only on most namespace resources, no secrets |
Security Pitfalls to Avoid
1. Wildcard Verbs and Resources
# Never do this
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
This is functionally equivalent to cluster-admin. Wildcards bypass the entire purpose of RBAC. Always enumerate the specific resources and verbs you need.
2. cluster-admin Abuse
cluster-admin is regularly granted because it "just works" — but it means the subject can modify RBAC itself, read all secrets cluster-wide, and delete any resource. Reserve it for break-glass emergency users and the human running cluster upgrades. Never bind it to a service account used by automation.
Check who has it:
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects'
3. Secrets Access as an Afterthought
Secrets are not covered by the view ClusterRole intentionally — but the edit and admin ClusterRoles do include them. If you give a service account edit access to a namespace, it can read all secrets in that namespace, including other services' credentials. Use a purpose-specific Role with explicit resources instead of built-in ClusterRoles for service accounts.
4. Forgetting pods/exec and pods/log
These are sub-resources and must be explicitly listed:
- apiGroups: [""]
resources: ["pods/exec", "pods/log", "pods/portforward"]
verbs: ["create", "get"]
pods/exec lets a user exec into any pod in the namespace — which is equivalent to shell access on the node if the pod has host mounts or privileged containers. Grant it only to operators who genuinely need it.
5. Not Auditing Bindings
RBAC permissions accumulate over time. Run periodic audits:
# See all subjects bound to cluster-admin
kubectl get clusterrolebindings -o wide | grep cluster-admin
# See all role bindings in a namespace
kubectl get rolebindings -n production -o wide
# Check what permissions a service account has
kubectl auth can-i --list --as=system:serviceaccount:production:github-actions -n production
The kubectl auth can-i --list approach is the fastest way to verify a service account's actual effective permissions — use it after creating bindings to confirm they're correct.
For generating production-ready RBAC manifests for common patterns, the Kubernetes RBAC Generator covers read-only users, CI accounts, namespace admins, and custom roles.