Role and RoleBinding. Use ClusterRole and ClusterRoleBinding only for cluster-scoped resources, non-resource URLs, or cross-namespace tools.
- Service Account Minimization: Bind permissions to specific ServiceAccounts, not users or groups, for workload identities. Use projected tokens to avoid static secret management.
- Subresource Discipline: Explicitly deny or restrict access to high-risk subresources like
pods/exec, pods/attach, and secrets unless absolutely required.
2. Implementation Architecture
The architecture relies on a layered role definition:
- Base Roles: Granular roles defining specific verb/resource combinations.
- Aggregation Roles: Roles that select base roles via label selectors.
- Bindings:
RoleBinding or ClusterRoleBinding attaching aggregation roles to subjects.
Code Example: Aggregated Role Structure
This YAML demonstrates the aggregation pattern. The super-admin role aggregates both the standard Kubernetes admin and a custom manage-monitoring role.
# base-role-view-logs.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: base:view-logs
labels:
rbac.example.com/aggregate-to-view: "true"
rbac.example.com/aggregate-to-admin: "true"
rules:
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
---
# base-role-manage-monitoring.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: base:manage-monitoring
labels:
rbac.example.com/aggregate-to-admin: "true"
rules:
- apiGroups: ["monitoring.coreos.com"]
resources: ["prometheusrules", "servicemonitors"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
# aggregated-role-super-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: super-admin
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-admin: "true"
rules: [] # Rules are dynamically aggregated
---
# binding-workload-admin.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: app-sa-admin
namespace: production
subjects:
- kind: ServiceAccount
name: app-deployer
namespace: production
roleRef:
kind: ClusterRole
name: super-admin
apiGroup: rbac.authorization.k8s.io
3. Architecture Decisions and Rationale
- Empty Rules in Aggregated Roles: The
super-admin role has an empty rules array. This is intentional. It prevents manual drift; permissions are solely derived from labels. If a rule needs to change, you update the base role or its labels, not the aggregated role.
- Label Convention: Using a domain-prefixed label (
rbac.example.com/aggregate-to-admin) prevents collisions with Kubernetes system labels and allows clear ownership of the aggregation logic.
- RoleBinding over ClusterRoleBinding for Workloads: Even when using a
ClusterRole like super-admin, the binding is a RoleBinding scoped to the production namespace. This ensures the ServiceAccount only has admin privileges within that namespace, adhering to least privilege.
4. Handling Subresources and Wildcards
Wildcards must be banned in production designs. The following pattern explicitly handles subresources to prevent accidental escalation:
# Anti-pattern: Wildcard verb on pods
# rules:
# - apiGroups: [""]
# resources: ["pods"]
# verbs: ["*"]
# Best Practice: Explicit verbs and subresource isolation
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
# Note: pods/exec requires 'create' verb, not 'exec'
Pitfall Guide
1. The cluster-admin Trap
Mistake: Assigning cluster-admin to CI/CD service accounts or developer groups for convenience.
Impact: A compromised CI/CD token grants full cluster control, including the ability to modify RBAC itself, create persistent backdoors, and exfiltrate secrets.
Remediation: Create scoped roles for CI/CD that only permit actions on specific namespaces and resources (e.g., deployments, services). Use kubectl auth can-i to validate scopes.
2. Ignoring system:authenticated and system:anonymous
Mistake: Binding roles to system:authenticated to allow all users access to a resource.
Impact: This includes all service accounts in the cluster, including those created by attackers or compromised workloads.
Remediation: Bind to specific User, Group, or ServiceAccount subjects. Never bind to broad system groups unless strictly required for core cluster functionality.
3. Wildcard API Groups and Resources
Mistake: Using resources: ["*"] or apiGroups: ["*"].
Impact: This grants access to all current and future resources, including custom resources and sensitive subresources like nodes/status or secrets. It also grants access to non-resource URLs if not careful.
Remediation: List explicit resources. Use admission controllers to reject manifests containing wildcards in RBAC definitions.
4. Overlooking Subresource Permissions
Mistake: Assuming pods permission does not include pods/exec.
Impact: RBAC requires explicit permission for subresources. However, a wildcard on pods often includes subresources. Conversely, developers may grant pods access and assume they can debug, failing to add pods/exec, or they grant * on pods and inadvertently allow exec.
Remediation: Treat subresources as distinct security boundaries. Audit roles for pods/exec, pods/attach, secrets, and configmaps separately.
5. Static ServiceAccount Tokens
Mistake: Relying on default long-lived tokens mounted in pods.
Impact: Tokens are valid indefinitely and are stored as secrets. If a pod is compromised, the token can be used until manually revoked.
Remediation: Enable ServiceAccountTokenVolumeProjection. Configure pods to use short-lived, auto-rotating tokens with audience bounds. This limits the blast radius and provides expiration metadata.
6. RBAC vs. Admission Controller Confusion
Mistake: Using RBAC to enforce policy constraints (e.g., "users cannot create Ingress resources with specific annotations").
Impact: RBAC is not capable of attribute-based filtering. It only controls access to resources and verbs.
Remediation: Use OPA/Gatekeeper or Kyverno for policy enforcement. RBAC should only manage "who can do what," not "under what conditions."
7. Binding to Namespaces That Don't Exist
Mistake: Creating RoleBinding in a namespace that hasn't been created yet, or relying on dynamic namespace creation without ensuring RBAC is applied.
Impact: Gaps in security posture during namespace lifecycle events.
Remediation: Integrate RBAC provisioning into namespace creation workflows. Use ClusterPolicy or GitOps operators to ensure every namespace receives the required baseline bindings upon creation.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Multi-tenant SaaS | Strict Namespace Isolation + Aggregated Roles | Prevents cross-tenant data leakage and lateral movement. | High initial design, Low operational risk. |
| Dev/Test Cluster | Relaxed RBAC + Automated Cleanup | Prioritizes developer velocity; risk is contained in non-prod. | Low |
| CI/CD Pipeline | Projected ServiceAccount Tokens + Scoped Roles | Eliminates secret management; limits pipeline blast radius. | Low |
| External Partner Access | Identity Federation + Limited ClusterRoles | Avoids local user management; enforces external IdP policies. | Medium |
| Legacy Monolith Migration | Gradual Scoping + Audit Mode | Allows safe migration by logging denials before enforcement. | Medium |
Configuration Template
Copy this template to establish a baseline RBAC structure using aggregation. This template defines a readonly and editor capability set that can be composed into namespace roles.
# rbac-baseline.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: baseline:readonly
labels:
rbac.codcompass.io/aggregate-to-readonly: "true"
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "jobs", "services", "configmaps"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: baseline:editor
labels:
rbac.codcompass.io/aggregate-to-editor: "true"
rules:
- apiGroups: ["", "apps", "batch"]
resources: ["pods", "deployments", "jobs", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ns-readonly
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.codcompass.io/aggregate-to-readonly: "true"
rules: []
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ns-editor
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.codcompass.io/aggregate-to-editor: "true"
rules: []
---
# Example Binding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-frontend-editors
namespace: frontend
subjects:
- kind: Group
name: frontend-devs
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: ns-editor
apiGroup: rbac.authorization.k8s.io
Quick Start Guide
-
Install RBAC Analysis Tool:
Run kubectl krew install rbac-lookup or install kubectl-who-can via go install. These tools allow you to query permissions inversely (e.g., "Who can exec into pods?").
-
Scan for Drift:
Execute kubectl rbac-lookup cluster-admin --all-namespaces to identify all entities with cluster-admin rights. Document and review each binding.
-
Create a Restricted Role:
Apply the rbac-baseline.yaml template from the Configuration Bundle to your cluster.
-
Test Access:
Use kubectl auth can-i <verb> <resource> --as=system:serviceaccount:<namespace>:<sa> to verify that service accounts have the expected permissions and are denied unauthorized actions.
-
Enforce via Policy:
If using OPA/Gatekeeper, deploy a ConstraintTemplate that rejects ClusterRole manifests containing resources: ["*"] or verbs: ["*"]. This prevents regression into insecure patterns.