- k8s
- l1
- topic-pack
- k8s-rbac --- Portal | Level: L1: Foundations | Topics: RBAC | Domain: Kubernetes
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¶
- Identify the subject:
kubectl get rolebindings,clusterrolebindings -A -o json | jq '.items[] | select(.subjects[]?.name=="<subject>")' - Check what roles are bound: look at
roleRefin each binding - Inspect the role rules:
kubectl describe role <name> -n <namespace>orkubectl describe clusterrole <name> - Test with
can-i --as: simulate the identity to confirm access
Interview tip: "A service account gets
Forbiddenwhen 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 | jqto 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:
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¶
- Start with zero permissions — add only what is needed, never subtract from broad access
- Use namespace isolation — separate teams and environments with namespaces and RoleBindings
- Prefer groups over users — bind ClusterRoles to groups from your identity provider
- Disable default SA tokens — set
automountServiceAccountToken: falseon ServiceAccounts - Audit bindings quarterly — stale bindings are invisible privilege escalation paths
- Use aggregated ClusterRoles for CRDs — extend view/edit/admin cleanly
- Separate read and write roles — a reader role and a writer role compose better than one mixed role
- Never grant escalate or bind verbs — unless the subject genuinely manages RBAC
- Log and alert on Forbidden errors — spikes in 403s indicate misconfiguration or probing
- Version-control all RBAC manifests — RBAC drift is a security incident waiting to happen
Wiki Navigation¶
Related Content¶
- Interview: RBAC Forbidden (Scenario, L2) — RBAC
- Kubernetes Exercises (Quest Ladder) (CLI) (Exercise Set, L1) — RBAC
- Kubernetes RBAC Flashcards (CLI) (flashcard_deck, L1) — RBAC
- Kubernetes Security Flashcards (CLI) (flashcard_deck, L1) — RBAC
- Multi-Tenancy Patterns (Topic Pack, L2) — RBAC
- Policy Engines (OPA / Kyverno) (Topic Pack, L2) — RBAC
- Runbook: RBAC Forbidden (Runbook, L2) — RBAC
- Track: Kubernetes Core (Reference, L1) — RBAC
Pages that link here¶
- Anti-Primer: Kubernetes RBAC
- Certification Prep: CKA — Certified Kubernetes Administrator
- Certification Prep: CKAD — Certified Kubernetes Application Developer
- Certification Prep: CKS — Certified Kubernetes Security Specialist
- Comparison: Policy Engines
- K8S Rbac
- Kubernetes_Core
- Master Curriculum: 40 Weeks
- Multi-Tenancy Patterns
- Policy Engines (OPA / Kyverno)
- Production Readiness Review: Answer Key
- Production Readiness Review: Study Plans
- Runbook: RBAC Forbidden Error
- Scenario: RBAC Forbidden Error During Deploy
- Thinking Out Loud: Kubernetes RBAC