Knowing the RBAC primitives is step one. Applying them correctly — least privilege, preventing escalation, managing at scale — is where production security lives. This lesson covers the patterns that experienced K8s operators use.
1. Least Privilege Design
The principle: grant only the minimum permissions needed for the task, nothing more.
Anti-Patterns to Avoid
| Anti-Pattern | Risk | Fix |
|---|---|---|
Binding cluster-admin to developers | Full cluster compromise if credentials leak | Use edit or custom Role per namespace |
resources: ["*"] with verbs: ["*"] | Wildcard = no restrictions at all | List exact resources and verbs needed |
| ClusterRoleBinding when RoleBinding suffices | Grants access to ALL namespaces | Use RoleBinding per namespace |
Granting create pods to untrusted users | Pod creation = access to all namespace Secrets (via volume mounts) | Restrict pod creation or enforce Pod Security |
Granting list secrets | Exposes ALL secrets (worse than get which requires knowing the name) | Use get + resourceNames for specific secrets |
Least Privilege Checklist
# For each role, ask: # 1. What SPECIFIC resources does this workload/user need? # → List them explicitly (no wildcards) # 2. What SPECIFIC verbs are needed? # → Read-only: get, list, watch # → Deploy: get, list, create, update, patch # → Full control: all verbs (rare) # 3. What SCOPE is needed? # → Single namespace: Role + RoleBinding # → All namespaces: ClusterRole + ClusterRoleBinding (justify this!) # 4. Can we restrict to SPECIFIC resource names? # → resourceNames: ["specific-secret"] (narrow access)
Example: Layered Access for One Team
# Developer — can deploy but not touch infrastructure:
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["create", "update", "patch"]
# NO: secrets, pods/exec, RBAC roles, nodes, PVs
# On-call engineer — read everything + exec for debugging:
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["*"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec", "pods/log"]
verbs: ["create", "get"]
# NO: create/delete (read + debug only)
# Platform admin — full namespace control (but not cluster-wide):
# → Use built-in ClusterRole "admin" with RoleBinding per namespace
create pods is a powerful permission — treat it as implicitly granting Secret read access. Restrict it to trusted users/SAs, and enforce Pod Security Standards.
2. Aggregated ClusterRoles
Aggregation lets you compose ClusterRoles from smaller pieces using label selectors. When a new CRD is installed, it can add permissions to existing roles without modifying them.
How It Works
# Base ClusterRole with aggregation rule:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-view
labels:
rbac.example.com/aggregate-to-monitoring: "true" # ← contributes rules
rules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["prometheuses", "alertmanagers"]
verbs: ["get", "list", "watch"]
---
# Aggregating ClusterRole (collects rules from labeled roles):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregate-monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # ← auto-populated from matched ClusterRoles!
Built-in Aggregation (How K8s Does It)
The built-in admin, edit, and view ClusterRoles use aggregation:
# The "edit" ClusterRole aggregates all roles labeled:
# rbac.authorization.k8s.io/aggregate-to-edit: "true"
# When you install a CRD (e.g., cert-manager), it adds:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cert-manager-edit
labels:
rbac.authorization.k8s.io/aggregate-to-edit: "true" # ← auto-added to "edit"
rbac.authorization.k8s.io/aggregate-to-admin: "true" # ← auto-added to "admin"
rules:
- apiGroups: ["cert-manager.io"]
resources: ["certificates", "issuers"]
verbs: ["create", "delete", "get", "list", "patch", "update"]
# Now anyone with "edit" role automatically gets cert-manager permissions!
# No need to update existing RoleBindings.
edit or admin bindings immediately get permissions for the new resources. This is how the K8s ecosystem stays composable.
view, edit, admin roles. Label them with rbac.authorization.k8s.io/aggregate-to-view: "true" etc. This gives your CRD "zero-config" RBAC integration — platform teams don't need to create new bindings.
3. Escalation Prevention
Kubernetes prevents privilege escalation: you cannot grant permissions you don't already have.
The Escalation Check
# If user "jane" has only "get pods" permission:
# She CANNOT create a RoleBinding that grants "delete pods"
# → API server rejects: "user jane cannot grant verb 'delete' on resource 'pods'"
# To create/update Roles and Bindings, you must ALREADY have:
# 1. Permission to create/update roles/rolebindings (meta-permission)
# 2. ALL the permissions contained in the role you're creating (escalation check)
# Exception: the "escalate" verb bypasses this check:
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "clusterroles"]
verbs: ["escalate"] # ← Allows creating roles with more perms than you have
# EXTREMELY DANGEROUS — equivalent to cluster-admin
The "bind" Verb
# The "bind" verb allows creating bindings to ANY role:
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "clusterrolebindings"]
verbs: ["create"] # Can create bindings...
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "clusterroles"]
verbs: ["bind"] # ...to any role (bypasses escalation check)
# ALSO DANGEROUS — can bind cluster-admin to themselves
escalate or bind verbs to non-admin users. These bypass the escalation prevention mechanism. A user with bind can attach themselves to cluster-admin. A user with escalate can create a Role granting any permission. Both are equivalent to full cluster compromise.
4. Dangerous Permission Combinations
| Permission | Why It's Dangerous |
|---|---|
create pods | Can mount any Secret in the namespace → read all secrets |
create pods + hostPath volumes | Can mount node filesystem → container escape → node root |
create pods/exec | Can exec into any Pod → access its SA token, env vars, mounted secrets |
list secrets | See ALL secrets in namespace (vs get which requires knowing the name) |
update/patch roles | Can expand their own permissions (if escalation check is bypassed) |
impersonate users | Can act as any user/SA — effectively cluster-admin |
create serviceaccounts + create rolebindings | Create a new SA, bind cluster-admin to it, use its token |
create tokenrequests (SA subresource) | Generate tokens for any SA → impersonate any workload |
create pods → mount Secrets → get SA token → if SA has more permissions than user → privilege escalation. This is why Pod Security Standards (restricting what Pods can do) are as important as RBAC (restricting who can create Pods). They're complementary layers.
5. Auditing & Debugging RBAC
# Who has access to what?
kubectl auth can-i --list --as=jane -n production
# Can this SA create deployments?
kubectl auth can-i create deployments \
--as=system:serviceaccount:ci:deployer -n production
# Find all RoleBindings in a namespace:
kubectl get rolebindings -n production -o wide
# NAME ROLE SUBJECTS
# dev-edit ClusterRole/edit User/jane, Group/developers
# Find all ClusterRoleBindings for a specific user:
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.subjects[]? | .name=="jane") | .metadata.name'
# What permissions does a ClusterRole grant?
kubectl describe clusterrole edit
# Audit: find overly permissive bindings (cluster-admin):
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") |
{name: .metadata.name, subjects: .subjects}'
Tools for RBAC Analysis
| Tool | Purpose |
|---|---|
kubectl auth can-i | Test specific permissions (built-in) |
rakkess | Show access matrix for a user (all resources × all verbs) |
kubectl-who-can | Find who can perform a specific action |
rbac-tool | Visualize and analyze RBAC policies |
kubescape | Security scanner that flags RBAC risks |
create pods and elevated permissions (escalation path). Tools like kubescape automate this.
Summary
| Pattern | Key Point |
|---|---|
| Least privilege | Explicit resources + verbs. No wildcards. Namespace-scoped where possible. |
| Aggregated ClusterRoles | Label-based composition. CRDs auto-extend view/edit/admin. |
| Escalation prevention | Can't grant perms you don't have. escalate/bind bypass this — never grant. |
| Pod creation = Secret access | Anyone who can create Pods can read all namespace Secrets. |
| Layered access | developer < on-call < namespace-admin < cluster-admin |
| Audit regularly | Find cluster-admin bindings, wildcard roles, stale access. |
📝 Quiz: RBAC Patterns
Q1: A developer has create pods permission in a namespace. Can they read Secrets in that namespace (even without explicit Secret get permission)?
create pods is nearly equivalent to get secrets in the same namespace. Mitigate with Pod Security Standards (restrict volume types) or admission policies.Q2: User jane has get pods permission. She tries to create a Role that grants delete pods. What happens?
delete pods, she can't create a Role granting it. This prevents users from escalating their own privileges through RBAC manipulation.Q3: You install cert-manager which creates a ClusterRole labeled aggregate-to-edit: "true". What happens to existing users with the "edit" ClusterRole?
Q4: What's the difference between granting list secrets vs get secrets with resourceNames?
list secrets: User can see ALL Secrets in the namespace (names + content). Very broad exposure.get secrets + resourceNames: ["db-creds"]: User can only read ONE specific Secret by name. They can't discover what other Secrets exist. Much more restrictive — prefer this when a workload only needs one specific Secret.Q5: Why is the impersonate verb considered equivalent to cluster-admin?
impersonate verb allows a user to act as ANY other user, group, or ServiceAccount — including system:masters (which has cluster-admin via a default ClusterRoleBinding). An attacker with impersonate can: kubectl --as=system:admin get secrets -A — full cluster access. Only grant impersonate to trusted automation that genuinely needs it (e.g., a multi-tenant proxy).Q6: How do you find all subjects that have cluster-admin access in your cluster?
kubectl get clusterrolebindings -o json | \ jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects[]'This lists all users, groups, and ServiceAccounts bound to the cluster-admin ClusterRole via ClusterRoleBindings. Also check RoleBindings in each namespace that reference cluster-admin (ClusterRole + RoleBinding = namespace-scoped cluster-admin).