Introduction#
Kubernetes RBAC controls who can perform what actions on which resources. Misconfigured RBAC is one of the most common Kubernetes security issues — overly permissive cluster-admin bindings or wildcard resource access can allow lateral movement after a compromise. This post covers the model and practical patterns for least-privilege configurations.
RBAC Model#
Four objects define RBAC:
- Role: namespaced permissions on resources
- ClusterRole: cluster-wide permissions (or reusable role templates)
- RoleBinding: binds a Role or ClusterRole to a subject within a namespace
- ClusterRoleBinding: binds a ClusterRole to a subject cluster-wide
Subjects can be: Users, Groups, or ServiceAccounts.
Defining Roles#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # Role: read-only access to pods and logs in production namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""] # "" = core API group
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
# ClusterRole: reusable across namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deployment-manager
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
|
Binding Roles to Subjects#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # RoleBinding: grant pod-reader to a user in production namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-pod-reader
namespace: production
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
---
# Grant the ClusterRole in a specific namespace (scoped binding)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: ci-deployment-manager
namespace: production
subjects:
- kind: ServiceAccount
name: ci-runner
namespace: ci
roleRef:
kind: ClusterRole
name: deployment-manager
apiGroup: rbac.authorization.k8s.io
|
ServiceAccount RBAC for Pods#
Pods use ServiceAccounts to call the Kubernetes API.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| # Create a dedicated service account for the app
apiVersion: v1
kind: ServiceAccount
metadata:
name: api-service
namespace: production
automountServiceAccountToken: false # opt-in only
---
# Grant minimal permissions
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: api-role
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"] # restrict to specific resource name
verbs: ["get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-role-binding
namespace: production
subjects:
- kind: ServiceAccount
name: api-service
namespace: production
roleRef:
kind: Role
name: api-role
apiGroup: rbac.authorization.k8s.io
---
# Pod spec: use the service account
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: production
spec:
template:
spec:
serviceAccountName: api-service
automountServiceAccountToken: true # explicitly mount the token
|
Auditing Existing Permissions#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Check what a user can do
kubectl auth can-i list pods --namespace production --as alice@example.com
kubectl auth can-i "*" "*" # check for cluster-admin
# List all permissions for a service account
kubectl auth can-i --list --as system:serviceaccount:production:api-service -n production
# Find all ClusterRoleBindings with cluster-admin
kubectl get clusterrolebinding -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects'
# Find all wildcard permissions (dangerous)
kubectl get roles,clusterroles -A -o json | \
jq '.items[] | select(.rules[].verbs[] == "*") | .metadata.name'
# Check for overly broad RBAC
rbac-tool lookup --subject User:alice@example.com
|
Common Anti-Patterns to Avoid#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # DANGEROUS: wildcard resources and verbs
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
# DANGEROUS: giving cluster-admin to a namespace
# (this bypasses all namespace scoping)
roleRef:
kind: ClusterRole
name: cluster-admin
# DANGEROUS: automounting service account tokens unnecessarily
# Every pod that doesn't need the API should set:
automountServiceAccountToken: false
|
CI/CD Pipeline RBAC#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # Minimal permissions for a CI/CD pipeline deploying to production
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: ci-deployer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "update", "patch"]
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# Note: no create/delete on deployments
# Deployments exist; CI only updates the image
|
Conclusion#
RBAC should follow least-privilege: grant only the verbs, resources, and namespaces actually needed. Use resourceNames to restrict access to specific named resources. Audit bindings regularly with kubectl auth can-i --list and flag any cluster-admin binding not explicitly justified. Disable automatic service account token mounting on pods that don’t call the Kubernetes API.