Kubernetes RBAC: Least Privilege in Practice

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

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.

Contents