Skip to content
Codeloom
Kubernetes

RBAC in Kubernetes: A Practical Guide

A practical guide to Kubernetes RBAC: Roles, ClusterRoles, RoleBindings, ClusterRoleBindings, ServiceAccounts, and real-world patterns for securing cluster access.

·8 min read · By Codeloom
Intermediate 14 min read

What you'll learn

  • How RBAC authorization works in Kubernetes
  • Creating Roles and ClusterRoles with specific permissions
  • Binding roles to users, groups, and ServiceAccounts
  • ServiceAccount patterns for pods and automation
  • Real-world RBAC configurations for teams and CI/CD

Prerequisites

  • Kubernetes fundamentals (Pods, Deployments, Namespaces)
  • Basic kubectl usage
  • Understanding of authentication vs authorization concepts

RBAC (Role-Based Access Control) controls who can do what in your Kubernetes cluster. Without RBAC, anyone with cluster access can create, modify, or delete any resource. With RBAC, you define precise permissions: this user can only read pods in this namespace, that service account can only create deployments in that namespace. Getting RBAC right is fundamental to running Kubernetes securely.

RBAC concepts

RBAC has four key resources:

  • Role — Defines permissions within a single namespace
  • ClusterRole — Defines permissions cluster-wide or across namespaces
  • RoleBinding — Grants a Role to a user, group, or ServiceAccount within a namespace
  • ClusterRoleBinding — Grants a ClusterRole cluster-wide

The relationship is simple: a Role says “what can be done,” and a RoleBinding says “who can do it.”

Role (what)        +  RoleBinding (who)        =  Permission in a namespace
ClusterRole (what) +  ClusterRoleBinding (who) =  Permission cluster-wide
ClusterRole (what) +  RoleBinding (who)        =  Permission in a namespace (reusable role)

Creating a Role

A Role specifies a list of rules. Each rule grants access to specific API resources with specific verbs.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: development
rules:
  - apiGroups: [""]           # Core API group (pods, services, etc.)
    resources: ["pods"]
    verbs: ["get", "watch", "list"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get"]
kubectl apply -f role.yaml
kubectl get roles -n development
kubectl describe role pod-reader -n development

Understanding the rules

apiGroups — The API group containing the resource. Core resources (pods, services, configmaps) use "". Apps resources (deployments, statefulsets) use "apps". Batch resources (jobs, cronjobs) use "batch".

resources — The resource type. Use kubectl api-resources to see all available resources.

verbs — The allowed operations:

  • get — Read a single resource
  • list — List all resources
  • watch — Stream changes
  • create — Create new resources
  • update — Modify existing resources
  • patch — Partially modify resources
  • delete — Delete resources
  • deletecollection — Delete multiple resources
# A more comprehensive developer role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
  namespace: development
rules:
  # Full access to common workload resources
  - apiGroups: ["", "apps", "batch"]
    resources:
      - pods
      - deployments
      - services
      - configmaps
      - jobs
      - cronjobs
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

  # Read-only access to secrets (can view but not modify)
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list"]

  # Can view logs and exec into pods (for debugging)
  - apiGroups: [""]
    resources: ["pods/log", "pods/exec"]
    verbs: ["get", "create"]

  # Can view events
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["get", "list", "watch"]

Creating a ClusterRole

ClusterRoles work like Roles but are not namespaced. They define permissions that can be applied cluster-wide or reused across namespaces.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: namespace-admin
rules:
  # Manage most resources within any namespace (when bound with RoleBinding)
  - apiGroups: ["", "apps", "batch", "networking.k8s.io"]
    resources: ["*"]
    verbs: ["*"]

  # But NOT cluster-scoped resources like nodes, namespaces, PVs
# Read-only ClusterRole for monitoring
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-viewer
rules:
  - apiGroups: [""]
    resources:
      - pods
      - services
      - endpoints
      - namespaces
      - nodes
      - persistentvolumes
      - persistentvolumeclaims
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources:
      - deployments
      - replicasets
      - statefulsets
      - daemonsets
    verbs: ["get", "list", "watch"]
  - apiGroups: ["batch"]
    resources: ["jobs", "cronjobs"]
    verbs: ["get", "list", "watch"]

RoleBinding

A RoleBinding grants the permissions defined in a Role to specific subjects (users, groups, or service accounts).

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods
  namespace: development
subjects:
  # A user
  - kind: User
    name: alice
    apiGroup: rbac.authorization.k8s.io
  # A group
  - kind: Group
    name: developers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f rolebinding.yaml
kubectl get rolebindings -n development
kubectl describe rolebinding read-pods -n development

Binding a ClusterRole in a namespace

You can bind a ClusterRole with a RoleBinding to limit it to a specific namespace. This is useful for reusing role definitions across namespaces.

# Reuse the cluster-viewer ClusterRole, but only in the development namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-viewer
  namespace: development
subjects:
  - kind: Group
    name: junior-developers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-viewer
  apiGroup: rbac.authorization.k8s.io

ClusterRoleBinding

Grants permissions cluster-wide.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admins
subjects:
  - kind: Group
    name: platform-team
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-admin  # Built-in superuser role
  apiGroup: rbac.authorization.k8s.io

Be very cautious with ClusterRoleBindings. They grant permissions across ALL namespaces, including kube-system. The built-in cluster-admin ClusterRole has full access to everything.

ServiceAccounts

ServiceAccounts provide identities for Pods. Every Pod runs with a ServiceAccount, and RBAC rules determine what that ServiceAccount can do.

# Create a ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-service-account
  namespace: production

---
# Create a Role for the ServiceAccount
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: app-role
  namespace: production
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
    resourceNames: ["app-config"]  # Only this specific secret

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

---
# Use the ServiceAccount in a Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: production
spec:
  template:
    spec:
      serviceAccountName: app-service-account
      automountServiceAccountToken: true
      containers:
        - name: app
          image: my-app:latest

resourceNames for fine-grained access

The resourceNames field restricts access to specific named resources.

rules:
  # Can only read the "app-config" secret, not any other secret
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
    resourceNames: ["app-config"]

  # Can only delete specific deployments
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["delete"]
    resourceNames: ["canary-deployment"]

CI/CD ServiceAccount

A common pattern: a ServiceAccount for CI/CD that can deploy to a specific namespace.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: staging

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: deployer-role
  namespace: staging
rules:
  # Can manage deployments and services
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  # Can manage configmaps and secrets for app config
  - apiGroups: [""]
    resources: ["configmaps", "secrets"]
    verbs: ["get", "list", "create", "update", "patch"]
  # Can view pod status (for deploy verification)
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  # Can manage ingress
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "list", "create", "update", "patch"]

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-binding
  namespace: staging
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: staging
roleRef:
  kind: Role
  name: deployer-role
  apiGroup: rbac.authorization.k8s.io
# Create a token for the CI/CD ServiceAccount
kubectl create token ci-deployer -n staging --duration=1h

Testing RBAC permissions

# Check if a user/SA can perform an action
kubectl auth can-i get pods --namespace=development --as=alice
# yes

kubectl auth can-i delete deployments --namespace=production --as=alice
# no

# Check as a ServiceAccount
kubectl auth can-i get secrets --namespace=production \
  --as=system:serviceaccount:production:app-service-account
# yes (if bound)

# List all permissions for a user
kubectl auth can-i --list --as=alice --namespace=development

Built-in ClusterRoles

Kubernetes ships with several default ClusterRoles:

kubectl get clusterroles | grep -E '^(cluster-admin|admin|edit|view)\s'
  • cluster-admin — Full access to everything. Use sparingly.
  • admin — Full access within a namespace (when bound with RoleBinding). Can manage most resources except ResourceQuotas and the namespace itself.
  • edit — Read/write access to most resources in a namespace. No RBAC or role management.
  • view — Read-only access to most resources. No secrets access.
# Give a developer edit access to a namespace using a built-in role
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-edit
  namespace: development
subjects:
  - kind: User
    name: bob
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io

Aggregated ClusterRoles

Aggregated ClusterRoles combine rules from multiple ClusterRoles using label selectors.

# Base role that other roles aggregate into
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-view
  labels:
    rbac.authorization.k8s.io/aggregate-to-view: "true"
    rbac.authorization.k8s.io/aggregate-to-edit: "true"
rules:
  - apiGroups: ["monitoring.coreos.com"]
    resources: ["prometheusrules", "servicemonitors"]
    verbs: ["get", "list", "watch"]

Any ClusterRole with the label rbac.authorization.k8s.io/aggregate-to-view: "true" automatically has its rules merged into the built-in view ClusterRole. This is how CRD operators extend the default roles.

Common mistakes

  1. Using cluster-admin for everything. Start with the minimum required permissions and add more as needed.
  2. Not setting automountServiceAccountToken: false on Pods that do not need API access. Every Pod mounts the default ServiceAccount token, which can be exploited.
  3. Granting wildcard permissions (resources: ["*"], verbs: ["*"]). Be explicit about what you are granting.
  4. Forgetting namespace isolation. A RoleBinding only grants permissions in its own namespace. Users cannot accidentally access other namespaces unless you create ClusterRoleBindings.
  5. Not auditing RBAC regularly. Permissions accumulate over time. Review and prune them periodically.
# Disable auto-mounting for pods that don't need API access
apiVersion: v1
kind: Pod
metadata:
  name: web-server
spec:
  automountServiceAccountToken: false
  containers:
    - name: nginx
      image: nginx:1.27

RBAC is the gatekeeper for your entire cluster. Follow the principle of least privilege: grant the minimum permissions needed, scope them to the narrowest namespace possible, and audit regularly. Start with the built-in view and edit ClusterRoles, then create custom Roles only when you need finer control.