Skip to content

RBAC - Primer

Why This Matters

Role-Based Access Control is the authorization layer in Kubernetes. Every API request — from a human running kubectl, a CI pipeline deploying manifests, or a pod calling the API server — passes through RBAC. Misconfigure it and you either lock out legitimate users or hand cluster-admin to a compromised workload. RBAC is the difference between a contained incident and a full cluster takeover.

Timeline: Kubernetes RBAC was introduced as beta in Kubernetes 1.6 (March 2017) and reached general availability in Kubernetes 1.8 (October 2017). Before RBAC, Kubernetes used ABAC (Attribute-Based Access Control), which required restarting the API server to change policies — a non-starter for production. RBAC replaced ABAC as the default authorization mode because policies are defined as Kubernetes resources (Roles, ClusterRoles) that can be created, modified, and deleted via the API without restarting anything.

Remember: The RBAC model has four objects — mnemonic: 2R-2B (two Roles, two Bindings). Role (namespace-scoped permissions), ClusterRole (cluster-scoped permissions), RoleBinding (grants in one namespace), ClusterRoleBinding (grants everywhere). The binding type determines scope, not the role type. A ClusterRole + RoleBinding = scoped to one namespace. A ClusterRole + ClusterRoleBinding = cluster-wide.

Core Model

RBAC answers one question: Can this identity perform this verb on this resource in this scope?

The model has four object types:

Object Scope Purpose
Role Namespace Defines permissions within a single namespace
ClusterRole Cluster Defines permissions cluster-wide or across namespaces
RoleBinding Namespace Grants a Role or ClusterRole to subjects in a namespace
ClusterRoleBinding Cluster Grants a ClusterRole to subjects across the entire cluster

Role vs ClusterRole

A Role grants access to resources within a specific namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]

A ClusterRole grants access cluster-wide. It is required for cluster-scoped resources (nodes, namespaces, PVs) and for granting the same permissions across all namespaces:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: secret-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["get", "list"]

A ClusterRole can also be referenced by a namespace-scoped RoleBinding — this lets you define permissions once and grant them per-namespace.

RoleBinding vs ClusterRoleBinding

A RoleBinding grants permissions within one namespace. It can reference either a Role or a ClusterRole:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
  - kind: User
    name: alice
    apiGroup: rbac.authorization.k8s.io
  - kind: ServiceAccount
    name: ci-deployer
    namespace: ci
roleRef:
  kind: ClusterRole
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

A ClusterRoleBinding grants permissions across all namespaces:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: global-secret-reader
subjects:
  - kind: Group
    name: sre-team
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: secret-reader
  apiGroup: rbac.authorization.k8s.io

Key distinction: a ClusterRole + RoleBinding = scoped permissions in one namespace. A ClusterRole + ClusterRoleBinding = permissions everywhere. The binding type determines the scope, not the role type.

Verbs

RBAC verbs map to API operations:

Verb HTTP Method Description
get GET (single) Read a specific resource
list GET (collection) List resources
watch GET (streaming) Stream changes to resources
create POST Create a new resource
update PUT Replace an entire resource
patch PATCH Modify specific fields
delete DELETE (single) Delete a resource
deletecollection DELETE (collection) Delete multiple resources at once

Additional special verbs: bind (create RoleBindings referencing a role), escalate (modify roles beyond your own permissions), impersonate (act as another identity), use (PodSecurityPolicies/runtime classes).

ServiceAccounts

Every pod runs as a ServiceAccount. If none is specified, it uses the default SA in the namespace:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-backend
  namespace: production
automountServiceAccountToken: false

Assign it to a pod:

spec:
  serviceAccountName: app-backend
  automountServiceAccountToken: true  # override SA-level setting if needed

Critical security practice: set automountServiceAccountToken: false on the ServiceAccount and only enable it on pods that actually need API access. Most application pods never call the Kubernetes API — they do not need a token.

Least Privilege Patterns

Pattern 1: Namespace-scoped deployer

A CI service account that can deploy to one namespace only:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer
  namespace: staging
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services", "configmaps"]
    verbs: ["get", "list", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]

Pattern 2: Restrict by resource name

Limit access to specific named resources:

rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["app-config", "feature-flags"]
    verbs: ["get", "update"]

Aggregated ClusterRoles

Aggregation lets you compose ClusterRoles from smaller pieces using label selectors. The built-in admin, edit, and view roles use this:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: custom-crd-viewer
  labels:
    rbac.authorization.k8s.io/aggregate-to-view: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
  - apiGroups: ["myapp.example.com"]
    resources: ["widgets"]
    verbs: ["get", "list", "watch"]

By labeling this role with aggregate-to-view: "true", its rules are automatically merged into the built-in view ClusterRole. This is the correct way to extend the default roles for custom resources — never edit the built-in roles directly.

Debugging RBAC

kubectl auth can-i

The primary debugging tool. Check permissions from any identity's perspective:

# Can I create deployments in production?
kubectl auth can-i create deployments -n production

# Can service account "ci-deployer" in namespace "ci" list pods in "staging"?
kubectl auth can-i list pods -n staging --as=system:serviceaccount:ci:ci-deployer

# List all permissions for the current user in a namespace
kubectl auth can-i --list -n production

# Check cluster-scoped permissions
kubectl auth can-i create namespaces

Audit Logs

When auth can-i is not enough, check audit logs for RBAC denials. In managed clusters, use the cloud provider's audit log UI. In self-managed clusters, grep for Forbidden in /var/log/kubernetes/audit.log.

Common Debugging Steps

  1. Identify the subject: kubectl get rolebindings,clusterrolebindings -A -o json | jq '.items[] | select(.subjects[]?.name=="<subject>")'
  2. Check what roles are bound: look at roleRef in each binding
  3. Inspect the role rules: kubectl describe role <name> -n <namespace> or kubectl describe clusterrole <name>
  4. Test with can-i --as: simulate the identity to confirm access

Interview tip: "A service account gets Forbidden when trying to list pods. How do you debug it?" is a common Kubernetes interview question. The answer sequence: 1) kubectl auth can-i list pods -n <ns> --as=system:serviceaccount:<ns>:<sa> (confirm the denial), 2) kubectl get rolebindings,clusterrolebindings -A -o json | jq to find bindings for that SA, 3) inspect the referenced Role/ClusterRole rules, 4) check for typos in apiGroups, resources, or verbs. The most common cause: the RoleBinding is in the wrong namespace, or the Role is missing a verb.

Common Mistakes

1. Overly broad ClusterRoles

Granting cluster-admin or wildcard permissions to service accounts:

# DANGEROUS — never do this for workloads
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

If a pod with these permissions is compromised, the attacker owns the cluster. Always scope to the minimum required resources and verbs.

2. Default ServiceAccount permissions

The default ServiceAccount in every namespace starts with no permissions beyond discovery. But some clusters or Helm charts bind roles to default, meaning every pod in the namespace inherits those permissions. Audit:

kubectl get rolebindings,clusterrolebindings -A -o json | \
  jq '.items[] | select(.subjects[]?.name=="default")'

3. ClusterRoleBinding when RoleBinding suffices

Using ClusterRoleBinding gives access across ALL namespaces. If the workload only operates in one namespace, use a RoleBinding — even if the role is a ClusterRole.

4. Forgetting subresources

pods and pods/log are separate resources. Granting get on pods does NOT allow reading logs. Same for pods/exec, pods/portforward, deployments/scale.

5. Not revoking old access

When teams change or people leave, stale RoleBindings persist. Audit regularly with kubectl get rolebindings -A and review subjects. Map organizational groups (from OIDC or LDAP) to ClusterRoles instead of individual users — group-based bindings scale better and are easier to revoke.

Testing RBAC Policies

Before deploying RBAC changes to production, validate in a staging cluster. Create a test ServiceAccount, bind the role under test, then verify with kubectl auth can-i --as=system:serviceaccount:<ns>:<sa>. Check both positive cases (expected permissions return "yes") and negative cases (everything else returns "no"). Clean up the test SA and binding afterward.

Best Practices

  1. Start with zero permissions — add only what is needed, never subtract from broad access
  2. Use namespace isolation — separate teams and environments with namespaces and RoleBindings
  3. Prefer groups over users — bind ClusterRoles to groups from your identity provider
  4. Disable default SA tokens — set automountServiceAccountToken: false on ServiceAccounts
  5. Audit bindings quarterly — stale bindings are invisible privilege escalation paths
  6. Use aggregated ClusterRoles for CRDs — extend view/edit/admin cleanly
  7. Separate read and write roles — a reader role and a writer role compose better than one mixed role
  8. Never grant escalate or bind verbs — unless the subject genuinely manages RBAC
  9. Log and alert on Forbidden errors — spikes in 403s indicate misconfiguration or probing
  10. Version-control all RBAC manifests — RBAC drift is a security incident waiting to happen

Wiki Navigation