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-PatternRiskFix
Binding cluster-admin to developersFull cluster compromise if credentials leakUse edit or custom Role per namespace
resources: ["*"] with verbs: ["*"]Wildcard = no restrictions at allList exact resources and verbs needed
ClusterRoleBinding when RoleBinding sufficesGrants access to ALL namespacesUse RoleBinding per namespace
Granting create pods to untrusted usersPod creation = access to all namespace Secrets (via volume mounts)Restrict pod creation or enforce Pod Security
Granting list secretsExposes 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
Pod creation ≈ Secret access. Anyone who can create Pods in a namespace can mount any Secret in that namespace into their Pod and read it. This is why 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.
Aggregation enables extensibility without rebinding. When a new operator/CRD is installed, it labels its RBAC rules to aggregate into the standard roles. Existing users with edit or admin bindings immediately get permissions for the new resources. This is how the K8s ecosystem stays composable.
When building custom operators/CRDs, always provide aggregated ClusterRoles that plug into the standard 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
Never grant 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

PermissionWhy It's Dangerous
create podsCan mount any Secret in the namespace → read all secrets
create pods + hostPath volumesCan mount node filesystem → container escape → node root
create pods/execCan exec into any Pod → access its SA token, env vars, mounted secrets
list secretsSee ALL secrets in namespace (vs get which requires knowing the name)
update/patch rolesCan expand their own permissions (if escalation check is bypassed)
impersonate usersCan act as any user/SA — effectively cluster-admin
create serviceaccounts + create rolebindingsCreate a new SA, bind cluster-admin to it, use its token
create tokenrequests (SA subresource)Generate tokens for any SA → impersonate any workload
The escalation chain: 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

ToolPurpose
kubectl auth can-iTest specific permissions (built-in)
rakkessShow access matrix for a user (all resources × all verbs)
kubectl-who-canFind who can perform a specific action
rbac-toolVisualize and analyze RBAC policies
kubescapeSecurity scanner that flags RBAC risks
Run RBAC audits quarterly: (1) Find all cluster-admin bindings — are they all justified? (2) Find ServiceAccounts with wildcard permissions. (3) Check for stale bindings (users who left the company). (4) Verify no SA has both create pods and elevated permissions (escalation path). Tools like kubescape automate this.

Summary

PatternKey Point
Least privilegeExplicit resources + verbs. No wildcards. Namespace-scoped where possible.
Aggregated ClusterRolesLabel-based composition. CRDs auto-extend view/edit/admin.
Escalation preventionCan't grant perms you don't have. escalate/bind bypass this — never grant.
Pod creation = Secret accessAnyone who can create Pods can read all namespace Secrets.
Layered accessdeveloper < on-call < namespace-admin < cluster-admin
Audit regularlyFind 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)?

Effectively yes. They can create a Pod that mounts any Secret as a volume, then exec into it (or read logs) to see the Secret content. This is why 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?

Rejected. The API server's escalation prevention check verifies that jane already has all permissions in the Role she's trying to create. Since she doesn't have 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?

They automatically get cert-manager permissions (create/edit Certificates, Issuers, etc.) without any binding changes. The built-in "edit" ClusterRole aggregates rules from all ClusterRoles with that label. No rebinding needed — permissions extend dynamically via label-based aggregation.

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?

The 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).