RBAC Patterns#
Kubernetes RBAC controls who can do what to which resources. It is built on four objects: Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings. Getting RBAC right means understanding how these four pieces compose and knowing the common patterns that cover 90% of real-world needs.
The Four RBAC Objects#
Role – Defines permissions within a single namespace. Lists API groups, resources, and allowed verbs.
ClusterRole – Defines permissions cluster-wide or for non-namespaced resources (nodes, persistent volumes, namespaces themselves).
RoleBinding – Grants a Role or ClusterRole to a user, group, or ServiceAccount within a specific namespace.
ClusterRoleBinding – Grants a ClusterRole to a subject across all namespaces.
The key insight: a ClusterRole combined with a RoleBinding limits that ClusterRole’s permissions to the binding’s namespace. This is the most useful pattern because you define permissions once (ClusterRole) and grant them selectively per namespace (RoleBinding).
Pattern 1: Read-Only Namespace Access#
Give a developer or team read-only visibility into a production namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-reader
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io"]
resources: ["pods", "services", "deployments", "replicasets",
"statefulsets", "daemonsets", "jobs", "cronjobs",
"configmaps", "ingresses", "events"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-team-readonly
namespace: payments-prod
subjects:
- kind: Group
name: dev-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-reader
apiGroup: rbac.authorization.k8s.ioNote pods/log as a sub-resource: without it, users can see pods but cannot read logs. Secrets are deliberately excluded – read access to Secrets is effectively the same as having the values.
Pattern 2: CI/CD Deploy Service Account#
A service account for your CI/CD pipeline that can deploy applications but cannot read Secrets or modify RBAC.
apiVersion: v1
kind: ServiceAccount
metadata:
name: ci-deploy
namespace: payments-prod
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployer
namespace: payments-prod
rules:
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deploy-binding
namespace: payments-prod
subjects:
- kind: ServiceAccount
name: ci-deploy
namespace: payments-prod
roleRef:
kind: Role
name: deployer
apiGroup: rbac.authorization.k8s.ioGenerate a short-lived token for CI:
kubectl create token ci-deploy -n payments-prod --duration=1hPattern 3: Monitoring Access#
Prometheus and similar tools need to scrape metrics across all namespaces but should not be able to modify anything.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-reader
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "services", "endpoints", "namespaces"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/proxy"]
verbs: ["get"]
- nonResourceURLs: ["/metrics"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: monitoring-reader-binding
subjects:
- kind: ServiceAccount
name: prometheus
namespace: monitoring
roleRef:
kind: ClusterRole
name: monitoring-reader
apiGroup: rbac.authorization.k8s.ioThis uses a ClusterRoleBinding because monitoring needs cluster-wide access. The nonResourceURLs rule grants access to the /metrics endpoint on the API server itself.
The Default Service Account Problem#
Every namespace has a default ServiceAccount, and every pod that does not specify one runs as default. This account may have more permissions than you expect. Disable its token mounting and create dedicated service accounts instead:
kubectl patch serviceaccount default -n payments-prod \
-p '{"automountServiceAccountToken": false}'For pods that do not need API access (most application pods), disable token mounting:
spec:
automountServiceAccountToken: false
containers:
- name: app
image: my-app:latestFor pods that need API access, create a dedicated service account with minimal permissions:
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: my-app:latestDebugging RBAC with kubectl auth can-i#
The can-i subcommand tests whether a subject has a specific permission. This is the fastest way to debug access issues.
Check your own permissions:
kubectl auth can-i create deployments -n payments-prod
# yes
kubectl auth can-i delete secrets -n payments-prod
# noCheck another user or service account:
kubectl auth can-i get pods -n payments-prod \
--as=system:serviceaccount:payments-prod:ci-deploy
# yes
kubectl auth can-i delete namespaces \
--as=system:serviceaccount:payments-prod:ci-deploy
# noList all permissions for a subject:
kubectl auth can-i --list -n payments-prod \
--as=system:serviceaccount:payments-prod:ci-deployThis outputs a table of every resource and verb the subject has access to in that namespace.
Check what ClusterRoles and RoleBindings apply to a subject:
# Find all bindings that reference a specific service account
kubectl get rolebindings,clusterrolebindings -A -o json | \
jq '.items[] | select(.subjects[]? | .name == "ci-deploy") | .metadata.name'Least Privilege Checklist#
- Never grant
*(wildcard) verbs or resources unless you are building a cluster admin role. - Exclude Secrets from read-only roles. Secret access should be explicit and limited.
- Disable automatic service account token mounting on the default service account.
- Use short-lived tokens (
kubectl create token) instead of long-lived secrets. - Prefer namespaced RoleBindings over ClusterRoleBindings. Cluster-wide access should be the exception.
- Audit with
kubectl auth can-i --listafter making changes to verify the effective permissions match your intent.