RBAC Gotchas and Common Mistakes
4.1 Core API Group Empty String Requirement
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-access
namespace: default
rules:
- apiGroups: ["v1"]
resources: ["pods"]
verbs: ["get", "list"]Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-access
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]Explanation: The empty string "" represents the core API group, which contains fundamental resources like pods, services, and configmaps. Using "v1" instead of "" will cause the RBAC rule to be ignored, resulting in access denial without clear error messages.
4.2 ServiceAccount Subject API Group Omission
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sa-binding
namespace: default
subjects:
- kind: ServiceAccount
name: my-app
namespace: default
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioCorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: sa-binding
namespace: default
subjects:
- kind: ServiceAccount
name: my-app
namespace: default
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioExplanation: ServiceAccount resources exist in the core API group, not the RBAC API group. The apiGroup field must be omitted when referencing ServiceAccounts as subjects. For Users and Groups, the apiGroup must be rbac.authorization.k8s.io.
4.3 RoleBinding Scope Limitation
Incorrect Expectation
User creates a RoleBinding in the development namespace and expects access to production namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-access
namespace: development
subjects:
- kind: User
name: developer
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioResult: The developer only has access in the development namespace, not in production or any other namespace.
Correct Approach for Multi-Namespace Access
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-access-dev
namespace: development
subjects:
- kind: User
name: developer
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-access-prod
namespace: production
subjects:
- kind: User
name: developer
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.ioExplanation: RoleBindings are always namespace-scoped. A single RoleBinding cannot grant access across multiple namespaces. To provide access in multiple namespaces, either create multiple RoleBindings or use a ClusterRoleBinding for cluster-wide access.
4.4 ClusterRole with RoleBinding Scope Limitation
Misunderstanding
Assuming ClusterRole automatically provides cluster-wide access.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-manager-binding
namespace: staging
subjects:
- kind: User
name: operator
roleRef:
kind: ClusterRole
name: pod-manager
apiGroup: rbac.authorization.k8s.ioResult: The operator only has pod management permissions in the staging namespace, not cluster-wide.
Correct Implementation for Cluster-Wide Access
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pod-manager-global
subjects:
- kind: User
name: operator
roleRef:
kind: ClusterRole
name: pod-manager
apiGroup: rbac.authorization.k8s.ioExplanation: The scope of permissions is determined by the binding type, not the role type. A ClusterRole bound with a RoleBinding becomes namespace-scoped. For cluster-wide access, a ClusterRoleBinding must be used.
4.5 Subresource Permission Requirements
Incorrect Implementation
Assuming pod access includes subresource access.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-access
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]Result: Users can see pods but cannot view logs or execute commands inside containers.
Correct Implementation with Subresources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-access
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]Explanation: Subresources like logs, exec, port-forward, and attach require explicit permissions separate from the parent resource. Each subresource must be listed individually with appropriate verbs.
4.6 ResourceNames Limitation with List Verb
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-access
namespace: default
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-key"]
verbs: ["get", "list"]Result: Users can get specific secrets by name but cannot list all secrets.
Correct Implementation Separating Actions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-access
namespace: default
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["list"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-key"]
verbs: ["get"]Explanation: The list verb is incompatible with resourceNames. When resourceNames is specified, users can only access the named resources individually, not list all resources of that type. For list access, a separate rule without resourceNames is required.
4.7 Cross-Namespace Role Reference Prohibition
Incorrect Setup
Attempting to reference a Role from another namespace.
# Role exists in development namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: development
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]# RoleBinding attempts to reference it from production namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: production
subjects:
- kind: User
name: operator
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioResult: The binding fails validation or becomes invalid.
Correct Solution
# Option 1: Create duplicate Role in production namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
# Option 2: Use ClusterRole and RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: production
subjects:
- kind: User
name: operator
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.ioExplanation: Role resources are namespace-scoped and cannot be referenced across namespace boundaries. A RoleBinding can only reference a Role in the same namespace. For cross-namespace permission reuse, ClusterRoles should be used.
4.8 Cluster-Scoped Resource Access with RoleBinding
Incorrect Expectation
Attempting to grant node access through a RoleBinding.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-viewer
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: node-access
namespace: default
subjects:
- kind: User
name: viewer
roleRef:
kind: ClusterRole
name: node-viewer
apiGroup: rbac.authorization.k8s.ioResult: The user cannot access nodes despite the binding.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: node-access-global
subjects:
- kind: User
name: viewer
roleRef:
kind: ClusterRole
name: node-viewer
apiGroup: rbac.authorization.k8s.ioExplanation: Cluster-scoped resources like nodes, persistentvolumes, and namespaces cannot be accessed through namespace-scoped bindings. RoleBindings can only grant access to namespace-scoped resources, even when referencing a ClusterRole. For cluster-scoped resource access, ClusterRoleBinding must be used.
4.9 Missing Default ServiceAccount Permissions
Common Misunderstanding
Assuming ServiceAccounts have default permissions.
apiVersion: v1
kind: Pod
metadata:
name: my-app
namespace: default
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: myapp:latestResult: The pod cannot access any Kubernetes API resources.
Required RBAC Setup
# First, create the ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: default
---
# Then, create permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: my-app-role
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# Finally, bind them
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-binding
namespace: default
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: default
roleRef:
kind: Role
name: my-app-role
apiGroup: rbac.authorization.k8s.ioExplanation: ServiceAccounts have no permissions by default. Unlike some cloud providers' IAM systems, Kubernetes RBAC requires explicit granting of all permissions. Each ServiceAccount starts with zero access and must be explicitly bound to Roles or ClusterRoles.
4.10 Verb Wildcard Incompatibility
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: wildcard-role
namespace: default
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["*"]Result: The wildcard verb may not work as expected with resourceNames.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: config-access
namespace: default
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get", "update", "patch", "delete"]Explanation: When using resourceNames, explicitly list the required verbs instead of using wildcards. Some verbs like list and watch may not be compatible with resourceNames restrictions.
4.11 API Group Specification for Custom Resources
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: crd-access
rules:
- apiGroups: [""]
resources: ["customresourcedefinitions"]
verbs: ["get", "list"]Result: Cannot access CRDs or custom resources.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: crd-access
rules:
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: custom-resource-access
rules:
- apiGroups: ["mycompany.com"]
resources: ["myresources"]
verbs: ["get", "list", "create"]Explanation: CustomResourceDefinitions are in the apiextensions.k8s.io API group, not the core group. Custom resources themselves are in their own API groups defined by their CRD. Both the CRD access and the custom resource access require separate rules with correct API groups.
4.12 Role Binding Subject Namespace Requirement
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: invalid-binding
namespace: app-ns
subjects:
- kind: ServiceAccount
name: monitor-sa
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioResult: Binding fails validation or ServiceAccount reference is invalid.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: valid-binding
namespace: app-ns
subjects:
- kind: ServiceAccount
name: monitor-sa
namespace: monitoring-ns
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioExplanation: When referencing a ServiceAccount in a RoleBinding, the namespace field is required in the subject definition. This is because ServiceAccounts are namespace-scoped resources, and the binding needs to know which namespace contains the ServiceAccount being referenced.
4.13 Non-Resource URL Access Requirements
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: health-checker
namespace: default
rules:
- apiGroups: [""]
resources: ["/healthz"]
verbs: ["get"]Result: Cannot access health check endpoints.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: health-checker
rules:
- nonResourceURLs: ["/healthz", "/readyz", "/livez"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: health-check-binding
subjects:
- kind: User
name: health-monitor
roleRef:
kind: ClusterRole
name: health-checker
apiGroup: rbac.authorization.k8s.ioExplanation: Non-resource URLs like health check endpoints require ClusterRoles with nonResourceURLs field instead of resources field. They also require ClusterRoleBindings since these endpoints exist at the cluster level, not within namespaces.
4.14 Missing API Group for Apps Resources
Incorrect Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-access
namespace: default
rules:
- apiGroups: [""]
resources: ["deployments"]
verbs: ["get", "list"]Result: Cannot access deployments.
Correct Implementation
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-access
namespace: default
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]Explanation: Deployments, StatefulSets, and DaemonSets are in the apps API group, not the core API group. Many resources that were originally in extensions/v1beta1 have moved to stable API groups like apps/v1.
4.15 Aggregated ClusterRole Rule Inheritance
Incorrect Expectation
Assuming aggregated ClusterRoles automatically update.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: aggregated-role
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]Result: The rules are not aggregated to other ClusterRoles.
Correct Aggregation Setup
# Base ClusterRole that will receive aggregated rules
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-role
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # Rules will be aggregated here
---
# ClusterRole that contributes rules
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-pods
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# Another contributing ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring-services
labels:
rbac.example.com/aggregate-to-monitoring: "true"
rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list"]Explanation: Aggregated ClusterRoles use the aggregationRule field to dynamically collect rules from other ClusterRoles with matching labels. The base ClusterRole has empty rules initially, and contributing ClusterRoles must have the matching label. The aggregation is not automatic for regular ClusterRoles.
Summary of Key RBAC Principles
- Empty string represents the core API group
- ServiceAccount subjects do not use apiGroup field
- RoleBindings are always namespace-scoped
- ClusterRoles become namespace-scoped when bound with RoleBindings
- Subresources require explicit permissions
- ResourceNames is incompatible with list verb
- Roles cannot be referenced across namespaces
- Cluster-scoped resources require ClusterRoleBindings
- ServiceAccounts have no default permissions
- Custom resources have specific API groups
- Non-resource URLs require ClusterRoles with nonResourceURLs field
- Apps resources are in the apps API group, not core
Each example demonstrates a specific gotcha with both incorrect and correct implementations, showing exactly what fails and how to fix it.