Cloud Security

Kubernetes RBAC Guide: Roles, ClusterRoles, and Least Privilege

Master Kubernetes RBAC with Role vs ClusterRole distinctions, ServiceAccount binding, audit policy configuration, kubectl auth can-i, and common RBAC misconfigurations.

March 9, 20267 min readShipSafer Team

Kubernetes RBAC Guide: Roles, ClusterRoles, and Least Privilege

Kubernetes Role-Based Access Control (RBAC) is the primary mechanism for controlling who can do what in a cluster. Misconfigured RBAC is consistently in the top findings of Kubernetes security assessments — often granting cluster-admin to service accounts that need far less, or leaving overly permissive bindings from initial setup. This guide covers RBAC from first principles to production-ready configuration.

Core Concepts

Kubernetes RBAC has four resource types:

ResourceScopePurpose
RoleNamespaceGrants access to resources within one namespace
ClusterRoleCluster-wideGrants access to cluster-wide resources or resources in all namespaces
RoleBindingNamespaceBinds a Role or ClusterRole to a subject within a namespace
ClusterRoleBindingCluster-wideBinds a ClusterRole to a subject across all namespaces

The most important rule: use the narrowest scope possible. Use Role + RoleBinding before reaching for ClusterRole + ClusterRoleBinding.

Role: Namespace-Scoped Permissions

# role-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: production
rules:
  - apiGroups: [""]          # "" = core API group (pods, services, configmaps)
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list"]
kubectl apply -f role-pod-reader.yaml

Verbs in RBAC map to HTTP methods and operations:

  • get — read a specific resource by name
  • list — list all resources
  • watch — stream changes
  • create — create new resources
  • update — modify existing resources
  • patch — partially modify resources
  • delete — delete resources
  • deletecollection — delete all matching resources

ClusterRole: Cluster-Scoped Permissions

Use ClusterRole for:

  • Cluster-scoped resources: nodes, persistent volumes, namespaces
  • Access that must span all namespaces
  • Reusable roles that get bound per-namespace via RoleBinding
# clusterrole-node-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["metrics.k8s.io"]
    resources: ["nodes", "pods"]
    verbs: ["get", "list"]

RoleBinding and ClusterRoleBinding

Bind a Role to a user, group, or ServiceAccount:

# rolebinding-pod-reader.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: production
subjects:
  - kind: ServiceAccount
    name: monitoring-agent
    namespace: production
  - kind: User
    name: jane@example.com  # In OIDC-integrated clusters
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

You can bind a ClusterRole with a RoleBinding to restrict its scope to one namespace:

# Use ClusterRole but limit scope to one namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: deployment-manager
  namespace: staging
subjects:
  - kind: ServiceAccount
    name: deploy-bot
    namespace: staging
roleRef:
  kind: ClusterRole        # ClusterRole, not Role
  name: edit               # Built-in "edit" ClusterRole
  apiGroup: rbac.authorization.k8s.io

ServiceAccount Least Privilege

Every Pod runs with a ServiceAccount. By default, it uses the default ServiceAccount in its namespace. This account, by default, has no RBAC permissions — but many guides tell developers to bind cluster-admin to it, which is a critical mistake.

Best practices for ServiceAccounts:

# 1. Create a dedicated ServiceAccount per application
apiVersion: v1
kind: ServiceAccount
metadata:
  name: payment-service
  namespace: production
automountServiceAccountToken: false  # Disable unless the app needs the API

---
# 2. Create a minimal Role for the application
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: payment-service-role
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["payment-api-key"]  # Only this specific secret
    verbs: ["get"]

---
# 3. Bind the Role to the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payment-service-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: payment-service
    namespace: production
roleRef:
  kind: Role
  name: payment-service-role
  apiGroup: rbac.authorization.k8s.io

Set the ServiceAccount in the Pod spec:

spec:
  serviceAccountName: payment-service
  automountServiceAccountToken: false

kubectl auth can-i — Auditing Permissions

Verify what a user or service account can do before and after configuration changes:

# Can the current user create deployments in production?
kubectl auth can-i create deployments -n production

# Can a specific service account list secrets?
kubectl auth can-i list secrets -n production \
  --as=system:serviceaccount:production:payment-service

# List all actions the monitoring-agent can take in production
kubectl auth can-i --list -n production \
  --as=system:serviceaccount:production:monitoring-agent

# Can anyone in the group "developers" delete pods?
kubectl auth can-i delete pods -n production \
  --as-group=developers --as=fake-user

Audit Policy

Enable Kubernetes audit logging to track all API calls:

# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  # Log all secret access at RequestResponse level
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets"]

  # Log authentication failures
  - level: Metadata
    verbs: ["get", "list", "watch"]
    resources:
      - group: ""
        resources: ["configmaps"]
    namespaces: ["production", "staging"]

  # Log cluster-admin actions
  - level: RequestResponse
    users: ["cluster-admin"]

  # Default: log metadata for everything else
  - level: Metadata

Enable in the API server:

# kube-apiserver flags
--audit-log-path=/var/log/kubernetes/audit.log
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
--audit-log-maxage=30
--audit-log-maxbackup=10
--audit-log-maxsize=100

Common RBAC Misconfigurations

1. Wildcard Verbs and Resources

# BAD — grants everything
rules:
  - apiGroups: ["*"]
    resources: ["*"]
    verbs: ["*"]

# Also bad — grants everything in core API group
rules:
  - apiGroups: [""]
    resources: ["*"]
    verbs: ["*"]

2. Cluster-Admin Bindings to Service Accounts

# Find all cluster-admin bindings
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name == "cluster-admin") | .subjects'

Every cluster-admin binding to a ServiceAccount is a potential privilege escalation path if that pod is compromised.

3. Wildcard on Secrets

# BAD — can read ALL secrets in the namespace
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]

# Good — can only read specific secrets
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["my-app-secret", "my-app-tls"]
    verbs: ["get"]

4. Bind-Escalate Privilege

If a user can create RoleBindings or ClusterRoleBindings, they can grant themselves any permission that exists in any Role — effectively escalating to cluster-admin:

# Dangerous — allows privilege escalation
rules:
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings", "clusterrolebindings"]
    verbs: ["create", "update", "patch"]

Restrict RoleBinding creation to cluster admins only.

Built-in ClusterRoles

Kubernetes ships with several useful built-in ClusterRoles:

ClusterRoleDescription
viewRead-only access to most resources
editRead/write access, no RBAC or network policy changes
adminFull namespace access, can manage RBAC within namespace
cluster-adminFull cluster access — use only when absolutely necessary

Use edit or view as your starting point and grant additional permissions as needed.

RBAC Audit Tools

# rbac-tool — visualize and audit RBAC
kubectl rbac-tool who-can create pods
kubectl rbac-tool show --kind ServiceAccount --name default -n production

# rakkess — show access matrix
kubectl-rakkess --sa production:payment-service

# kube-bench — CIS benchmark including RBAC checks
kube-bench run --targets node,master

Summary Checklist

  • Use Role + RoleBinding instead of ClusterRole + ClusterRoleBinding wherever possible
  • No wildcard * on verbs, resources, or API groups in application roles
  • cluster-admin only bound to human admin users, never ServiceAccounts
  • Dedicated ServiceAccount per application with automountServiceAccountToken: false
  • Secret access scoped to specific resourceNames
  • No application ServiceAccount can create RoleBindings
  • Audit policy enabled and logs shipped to SIEM
  • Regular audit with kubectl auth can-i --list per ServiceAccount
  • Use view or edit built-in roles as a ceiling, not a floor

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.