Kubernetes RBAC with Examples

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Role and ClusterRole rules, RoleBinding and ClusterRoleBinding subjects, kubectl auth can-i verification, apps API group grants, ClusterRole in a namespaced RoleBinding, node read access, and pods/log subresources. Does not cover authentication setup, admission control, certificate creation, cluster-admin shortcuts, aggregated ClusterRoles, or authorization webhooks.
Related guides kubectl apply, patch and replace
kubectl logs, Events and describe

RBAC separates what is allowed from who receives the grant:

  • A Role or ClusterRole lists API verbs on resources
  • A binding connects that rule set to a user, group, or ServiceAccount

This walkthrough builds a read-only Pod role in namespace development, verifies it with kubectl auth can-i, then extends the same pattern to Deployments, cluster-scoped Nodes, and Pod subresources.

Creating ClusterRoles and ClusterRoleBindings requires cluster-scoped authorization. Every --as=system:serviceaccount:... check also requires the current kubeconfig identity to have ServiceAccount impersonation permission.


Understand Kubernetes RBAC

Role, ClusterRole, and binding scope

Four objects cover most exam and day-to-day tasks:

Object Scope Purpose
Role Namespace Defines permissions in one namespace
ClusterRole Cluster Defines reusable or cluster-scoped permissions
RoleBinding Namespace Grants a Role or ClusterRole inside one namespace
ClusterRoleBinding Cluster Grants a ClusterRole cluster-wide

Permissions and subjects stay separate:

  • Role or ClusterRole = what is allowed
  • Binding = who receives it and where

Authentication must succeed before RBAC runs. For how identity reaches the API server, see authentication and admission control. For ServiceAccount creation and token mounts, see Kubernetes ServiceAccounts.

API groups, resources, verbs, and subresources

A typical namespaced rule looks like this:

yaml
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
  • Core API resources such as Pods and Services use an empty string in apiGroups.
  • Named groups include apps for Deployments, rbac.authorization.k8s.io for Roles, and others.
  • resources use plural API names (pods, not pod).
  • verbs are Kubernetes authorization verbs (get, list, create, delete, …), not always one-to-one with HTTP methods.

Confirm group and resource names on your cluster:

bash
kubectl api-resources | grep -E '^(NAME|pods|deployments|nodes) '

Sample output:

output
NAME                                SHORTNAMES   APIVERSION              NAMESPACED   KIND
nodes                               no           v1                      false        Node
pods                                po           v1                      true         Pod
deployments                         deploy       apps/v1                 true         Deployment

Pods sit in the core group with NAMESPACED true. Nodes are cluster-scoped. Deployments belong to apps/v1.


Create a Namespaced Role and RoleBinding

Create the ServiceAccount and read-only Role

Create the lab namespace:

bash
kubectl create namespace development

Sample output:

output
namespace/development created

Create a Role that allows read-only Pod access:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: development
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]

Apply the Role:

bash
kubectl apply -f pod-reader-role.yaml

Sample output:

output
role.rbac.authorization.k8s.io/pod-reader created

The Role exists but grants nothing until you bind it to a subject. Create a dedicated ServiceAccount:

bash
kubectl create serviceaccount pod-reader -n development

Sample output:

output
serviceaccount/pod-reader created

Bind the Role to that account:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-reader-binding
  namespace: development
subjects:
- kind: ServiceAccount
  name: pod-reader
  namespace: development
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Apply the binding:

bash
kubectl apply -f pod-reader-binding.yaml

Sample output:

output
rolebinding.rbac.authorization.k8s.io/pod-reader-binding created

Inspect what the binding connects:

bash
kubectl describe rolebinding pod-reader-binding -n development

Sample output:

output
Name:         pod-reader-binding
Role:
  Kind:  Role
  Name:  pod-reader
Subjects:
  Kind            Name        Namespace
  ----            ----        ---------
  ServiceAccount  pod-reader  development

The ServiceAccount subject needs all three fields: kind, name, and namespace.

Verify permissions with kubectl auth can-i

These commands use API impersonation. They evaluate authorization as the named ServiceAccount but do not test its token. Your current kubeconfig identity must be allowed to impersonate ServiceAccounts.

Impersonate the ServiceAccount API identity:

bash
kubectl auth can-i get pods --as=system:serviceaccount:development:pod-reader -n development

Sample output:

output
yes

Test a verb that the Role does not grant:

bash
kubectl auth can-i create pods --as=system:serviceaccount:development:pod-reader -n development

Sample output:

output
no

List effective permissions in the namespace:

bash
kubectl auth can-i --list --as=system:serviceaccount:development:pod-reader -n development

Sample output (trimmed):

output
Resources                                       Verbs
pods                                            [get list watch]

The table may also list cluster-default permissions your CNI or platform adds. Focus on the pods row for this Role.

Add Deployment access

Extend the same Role with Deployment read access. Save the complete updated manifest in pod-reader-role.yaml:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: development
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch"]

Apply the updated Role:

bash
kubectl apply -f pod-reader-role.yaml

Sample output:

output
role.rbac.authorization.k8s.io/pod-reader configured

Verify Deployment access:

bash
kubectl auth can-i get deployments --as=system:serviceaccount:development:pod-reader -n development

Sample output:

output
yes

Pod access remains unchanged:

bash
kubectl auth can-i get pods --as=system:serviceaccount:development:pod-reader -n development

Sample output:

output
yes

Deployment permissions do not imply Pod permissions, and the reverse is also true. Grant each resource explicitly.


Reuse a ClusterRole in One Namespace

A ClusterRole can hold reusable rules. A RoleBinding limits where those rules apply. Do not reuse pod-reader here—its existing RoleBinding already grants the tested Pod access.

Save a separate ServiceAccount and binding in reusable-pod-reader.yaml:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: reusable-pod-reader
  namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-pod-reader
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: cluster-pod-reader-binding
  namespace: development
subjects:
- kind: ServiceAccount
  name: reusable-pod-reader
  namespace: development
roleRef:
  kind: ClusterRole
  name: cluster-pod-reader
  apiGroup: rbac.authorization.k8s.io

Apply the manifest:

bash
kubectl apply -f reusable-pod-reader.yaml

Then test the isolated identity:

bash
kubectl auth can-i get pods --as=system:serviceaccount:development:reusable-pod-reader -n development

Sample output:

output
yes

The same identity cannot read Pods in default:

bash
kubectl auth can-i get pods --as=system:serviceaccount:development:reusable-pod-reader -n default

Sample output:

output
no

A RoleBinding can reference a ClusterRole, but it grants the namespaced portions of that ClusterRole only within the RoleBinding's namespace.


Grant Cluster-Wide Access

Create a Node reader

Cluster-scoped resources such as Nodes need a ClusterRole and ClusterRoleBinding. Save all three objects in node-reader-rbac.yaml:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: node-reader
  namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: node-reader-binding
subjects:
- kind: ServiceAccount
  name: node-reader
  namespace: development
roleRef:
  kind: ClusterRole
  name: node-reader
  apiGroup: rbac.authorization.k8s.io

Apply the manifest:

bash
kubectl apply -f node-reader-rbac.yaml

Sample output:

output
serviceaccount/node-reader created
clusterrole.rbac.authorization.k8s.io/node-reader created
clusterrolebinding.rbac.authorization.k8s.io/node-reader-binding created

Test cluster-scoped and namespaced permissions

Test node read access:

bash
kubectl auth can-i get nodes --as=system:serviceaccount:development:node-reader

Sample output:

output
yes

The node-reader identity has no Pod access in development:

bash
kubectl auth can-i get pods --as=system:serviceaccount:development:node-reader -n development

Sample output:

output
no

A ClusterRoleBinding cannot reference a namespaced Role. Use ClusterRole for cluster-wide grants.


Authorize Pod Logs and Exec Separately

Reading Pod objects does not automatically allow kubectl logs or kubectl exec. Those use subresources. Keep pods and pods/log as separate rule entries so the verbs for the main resource and subresource remain explicit.

Save the ServiceAccount, Role, and RoleBinding in log-reader-rbac.yaml:

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: log-reader
  namespace: development
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: log-reader
  namespace: development
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: log-reader
  namespace: development
subjects:
- kind: ServiceAccount
  name: log-reader
  namespace: development
roleRef:
  kind: Role
  name: log-reader
  apiGroup: rbac.authorization.k8s.io

Apply the manifest:

bash
kubectl apply -f log-reader-rbac.yaml

Then test:

bash
kubectl auth can-i get pods/log --as=system:serviceaccount:development:log-reader -n development

Sample output:

output
yes

Exec requires its own grant. The log-reader Role does not include it:

bash
kubectl auth can-i create pods/exec --as=system:serviceaccount:development:log-reader -n development

Sample output:

output
no

Because this Role also lists pods, the same identity can get Pod objects. A tighter Role with only pods/log would allow log access without listing Pods.


Bind Users and Groups

Bindings accept any authenticated subject. RBAC references the identity; it does not create it.

User subject:

yaml
subjects:
- kind: User
  name: jane
  apiGroup: rbac.authorization.k8s.io

Group subject:

yaml
subjects:
- kind: Group
  name: dev-team
  apiGroup: rbac.authorization.k8s.io

The name must match what your authentication method returns after the API server validates credentials.


Modify and Troubleshoot RBAC

Immutable roleRef

  • You can update rules on a Role or ClusterRole in place.
  • roleRef on a RoleBinding or ClusterRoleBinding is immutable.
  • To point a binding at a different Role, delete the binding and recreate it.
  • You can add or remove subjects on an existing binding.

Do not use cluster-admin as a routine fix. Grant the narrowest Role or ClusterRole that satisfies the task.

Forbidden-error workflow

When the API returns 403 Forbidden, work through this sequence:

  1. Read the exact user, verb, resource, and namespace in the error message.
  2. Confirm which identity the caller uses (kubeconfig user or system:serviceaccount:...).
  3. Run kubectl auth can-i with --as for that identity.
  4. List RoleBindings and ClusterRoleBindings that reference the subject.
  5. Inspect rules on the referenced Role or ClusterRole.
  6. Check API group, subresource (pods/log), and namespace scope.
  7. Re-test after you apply the correction.

Common scope and API-group mistakes

Mistake What goes wrong
Wrong API group Rule matches nothing; use kubectl api-resources
Singular resource name Use pods, not pod
RoleBinding in wrong namespace Grant applies only where the binding lives
ServiceAccount subject missing namespace Binding does not match the intended account
ClusterRole created but never bound Rules exist with no subject
RoleBinding expected to span namespaces Create a RoleBinding in each intended namespace, optionally referencing one reusable ClusterRole. Use ClusterRoleBinding only when the grant should apply cluster-wide
Pod access assumed to include pods/log Add pods/log explicitly for log access
Permission granted to wrong username can-i passes for admin but fails for the workload SA

RoleBindings remain limited to their own namespace even when they reference a ClusterRole.


What's Next


References


Summary

RBAC pairs rule objects with bindings. A Role or ClusterRole lists apiGroups, resources, and verbs. A RoleBinding or ClusterRoleBinding connects those rules to a user, group, or ServiceAccount in a specific scope. Namespace grants use Role plus RoleBinding; cluster-wide grants use ClusterRole plus ClusterRoleBinding.

The lab walked from a read-only Pod Role in development through kubectl auth can-i checks, an apps Deployment rule, a ClusterRole limited by RoleBinding to one namespace, and a node-reader ClusterRoleBinding for cluster-scoped reads. Subresources such as pods/log need their own resource entries; Pod read access does not imply log or exec access.

When troubleshooting, read the forbidden message, impersonate the caller with --as=system:serviceaccount:namespace:name, and trace bindings back to their rules. Remember that roleRef is immutable—update rules on the Role itself, or recreate the binding when you need a different target.


Frequently Asked Questions

1. What is the difference between a Role and a ClusterRole?

A Role defines permissions inside one namespace. A ClusterRole defines cluster-scoped permissions or reusable rules that can be bound into any namespace through a RoleBinding.

2. What is the difference between RoleBinding and ClusterRoleBinding?

A RoleBinding grants a Role or ClusterRole inside one namespace only. A ClusterRoleBinding grants a ClusterRole across the entire cluster. A ClusterRoleBinding cannot reference a namespaced Role.

3. How do I test whether a ServiceAccount can list Pods?

With a kubeconfig identity allowed to impersonate ServiceAccounts, run kubectl auth can-i list pods --as=system:serviceaccount:namespace:name -n namespace. This evaluates RBAC as that identity but does not test token authentication.

4. Does Pod read access include kubectl logs?

Not automatically. kubectl logs uses the pods/log subresource. Grant pods/log explicitly if the identity needs log access without broader Pod permissions.

5. Can I change the Role referenced by an existing RoleBinding?

No. The roleRef field in a binding is immutable. Delete the binding and create a new one with the updated roleRef, or patch the rules on the Role or ClusterRole itself.

6. Why does my RoleBinding not work across namespaces?

RoleBinding is always namespaced. It grants permissions only in the namespace where the binding object lives, even when it references a ClusterRole.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)