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.io

Note 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.io

Generate a short-lived token for CI:

kubectl create token ci-deploy -n payments-prod --duration=1h

Pattern 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.io

This 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:latest

For pods that need API access, create a dedicated service account with minimal permissions:

spec:
  serviceAccountName: my-app-sa
  containers:
  - name: app
    image: my-app:latest

Debugging 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
# no

Check 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
# no

List all permissions for a subject:

kubectl auth can-i --list -n payments-prod \
  --as=system:serviceaccount:payments-prod:ci-deploy

This 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#

  1. Never grant * (wildcard) verbs or resources unless you are building a cluster admin role.
  2. Exclude Secrets from read-only roles. Secret access should be explicit and limited.
  3. Disable automatic service account token mounting on the default service account.
  4. Use short-lived tokens (kubectl create token) instead of long-lived secrets.
  5. Prefer namespaced RoleBindings over ClusterRoleBindings. Cluster-wide access should be the exception.
  6. Audit with kubectl auth can-i --list after making changes to verify the effective permissions match your intent.