Skip to main content
Updated Feb 23, 2026

RBAC Deep Dive

In December 2023, a security researcher discovered that thousands of Kubernetes clusters on the public internet had exposed dashboards running with cluster-admin privileges. Attackers weren't exploiting vulnerabilities—they were using the default service account that Kubernetes automatically mounts into every pod, which often had more permissions than the application ever needed.

Your Task API doesn't need cluster-admin access. It doesn't need access to secrets across all namespaces. It needs exactly one thing: the ability to read its own ConfigMap. RBAC (Role-Based Access Control) lets you specify precisely that—and nothing more.

This lesson builds the RBAC foundation that protects your Task API from privilege escalation attacks. By the end, you'll understand why every production workload needs its own ServiceAccount, and you'll have the pattern to implement least privilege for any Kubernetes application.


What RBAC Actually Controls

Before creating RBAC resources, understand what you're protecting. Every Kubernetes API request goes through three checkpoints:

CheckpointQuestionDenial Result
Authentication"Who are you?"401 Unauthorized
Authorization (RBAC)"Are you allowed to do this?"403 Forbidden
Admission Control"Is this request valid?"Request rejected

RBAC handles the second checkpoint. When your Task API pod tries to read a ConfigMap, RBAC answers: "Is this ServiceAccount allowed to get ConfigMaps in this namespace?"


The Four RBAC Building Blocks

RBAC uses four types of resources that work together:

┌─────────────────────────────────────────────────────────────┐
│ RBAC Architecture │
├─────────────────────────────────────────────────────────────┤
│ │
│ SUBJECT BINDING ROLE │
│ (who) (connects) (what) │
│ │
│ ┌───────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ ServiceAccount├────►│ RoleBinding ├────►│ Role │ │
│ │ (task-api-sa) │ │ │ │ (configmap │ │
│ └───────────────┘ └──────────────┘ │ read only) │ │
│ └─────────────┘ │
│ │
│ Namespace-scoped: Role + RoleBinding │
│ Cluster-scoped: ClusterRole + ClusterRoleBinding │
└─────────────────────────────────────────────────────────────┘
ResourceScopePurpose
ServiceAccountNamespaceIdentity for pods (like a user account for applications)
RoleNamespaceDefines what actions are allowed on which resources
RoleBindingNamespaceConnects a ServiceAccount to a Role
ClusterRoleClusterLike Role, but applies across all namespaces
ClusterRoleBindingClusterConnects a ServiceAccount to a ClusterRole

Step 1: Create a Dedicated ServiceAccount

First, create a namespace for your Task API if it doesn't exist:

kubectl create namespace task-api

Output:

namespace/task-api created

Now create a dedicated ServiceAccount. The critical setting is automountServiceAccountToken: false, which prevents Kubernetes from automatically mounting the token into your pods. Create task-api-sa.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
name: task-api-sa
namespace: task-api
automountServiceAccountToken: false

Apply the ServiceAccount:

kubectl apply -f task-api-sa.yaml

Output:

serviceaccount/task-api-sa created

Why Disable Auto-Mounting?

By default, Kubernetes mounts a token into every pod at /var/run/secrets/kubernetes.io/serviceaccount/. This token grants API access. If your application doesn't need to call the Kubernetes API, mounting this token only creates attack surface.

Verify the setting:

kubectl get serviceaccount task-api-sa -n task-api -o yaml | grep automount

Output:

automountServiceAccountToken: false

Step 2: Define the Role (Minimum Required Permissions)

Your Task API needs to read its ConfigMap for configuration. It doesn't need to create, update, or delete ConfigMaps. It doesn't need access to Secrets. Define exactly that. Create task-api-role.yaml:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: task-api-role
namespace: task-api
rules:
- apiGroups: [""] # Core API group (ConfigMaps, Secrets, Pods, etc.)
resources: ["configmaps"] # Only ConfigMaps
verbs: ["get", "list"] # Read-only operations

Apply the Role:

kubectl apply -f task-api-role.yaml

Output:

role.rbac.authorization.k8s.io/task-api-role created

Understanding the Rules

Each rule has three components:

FieldExampleMeaning
apiGroups[""]Core API group (empty string means v1 resources like ConfigMaps, Pods)
resources["configmaps"]Which resource types this rule applies to
verbs["get", "list"]Allowed operations

Available verbs:

Verbkubectl equivalentDescription
getkubectl get [resource] [name]Read a single resource
listkubectl get [resources]List all resources of this type
watchkubectl get [resources] --watchStream updates
createkubectl createCreate new resources
updatekubectl apply (existing)Modify existing resources
patchkubectl patchPartial modification
deletekubectl deleteRemove resources
Never Use Wildcards

Using "*" for apiGroups, resources, or verbs grants far more access than needed. Explicit lists are always safer:

# DANGEROUS - avoid this
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]

# SAFE - explicit and minimal
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]

Step 3: Create the RoleBinding

The RoleBinding connects your ServiceAccount to the Role. Create task-api-binding.yaml:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: task-api-binding
namespace: task-api
subjects:
- kind: ServiceAccount
name: task-api-sa
namespace: task-api
roleRef:
kind: Role
name: task-api-role
apiGroup: rbac.authorization.k8s.io

Apply the RoleBinding:

kubectl apply -f task-api-binding.yaml

Output:

rolebinding.rbac.authorization.k8s.io/task-api-binding created

Step 4: Test RBAC with kubectl auth can-i

Before deploying your application, verify that permissions work as expected. The kubectl auth can-i command tests whether an action is allowed:

Test Allowed Operations

kubectl auth can-i get configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Output:

yes
kubectl auth can-i list configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Output:

yes

Test Denied Operations

kubectl auth can-i create configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Output:

no
kubectl auth can-i get secrets \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Output:

no
kubectl auth can-i get configmaps \
--as=system:serviceaccount:task-api:task-api-sa \
-n default

Output:

no

The ServiceAccount can read ConfigMaps in its own namespace, but cannot create them, cannot access Secrets, and cannot access resources in other namespaces.


Role vs ClusterRole: The Decision Matrix

When should you use Role (namespace-scoped) versus ClusterRole (cluster-scoped)?

ScenarioUseWhy
App reads its own ConfigMapRole + RoleBindingNamespace isolation is sufficient
App reads ConfigMaps in multiple namespacesClusterRole + RoleBinding per namespaceReuse role definition without cluster-wide access
CI/CD creates Deployments across namespacesClusterRole + RoleBinding per namespaceAvoid ClusterRoleBinding's full cluster scope
Monitoring reads all Pods cluster-wideClusterRole + ClusterRoleBindingLegitimate need for cluster-wide read access
Log aggregator collects from all namespacesClusterRole + ClusterRoleBindingMust access every namespace

Key Principle: Prefer RoleBinding Over ClusterRoleBinding

Even when using a ClusterRole, you can bind it with a RoleBinding to limit scope to one namespace:

# ClusterRole can be reused across namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: configmap-reader
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
# RoleBinding limits to specific namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-configmap-reader
namespace: development
subjects:
- kind: ServiceAccount
name: app-sa
namespace: development
roleRef:
kind: ClusterRole
name: configmap-reader
apiGroup: rbac.authorization.k8s.io

This pattern gives you reusable role definitions without cluster-wide access.


Deploying with the ServiceAccount

To use your ServiceAccount in a Deployment, specify it in the pod spec:

apiVersion: apps/v1
kind: Deployment
metadata:
name: task-api
namespace: task-api
spec:
replicas: 2
selector:
matchLabels:
app: task-api
template:
metadata:
labels:
app: task-api
spec:
serviceAccountName: task-api-sa
automountServiceAccountToken: false # Explicit override
containers:
- name: task-api
image: ghcr.io/your-org/task-api:v1.0.0
ports:
- containerPort: 8000

When the pod needs to access the Kubernetes API (for example, to read its ConfigMap), you must explicitly mount the token:

spec:
serviceAccountName: task-api-sa
automountServiceAccountToken: true # Enable when API access needed

Debugging RBAC Issues

When you see "403 Forbidden" errors, use these commands to diagnose:

Check What Permissions a ServiceAccount Has

kubectl auth can-i --list \
--as=system:serviceaccount:task-api:task-api-sa \
-n task-api

Output:

Resources                                       Non-Resource URLs   Resource Names   Verbs
configmaps [] [] [get list]
selfsubjectaccessreviews.authorization.k8s.io [] [] [create]
selfsubjectrulesreviews.authorization.k8s.io [] [] [create]

Describe RoleBinding to Verify Configuration

kubectl describe rolebinding task-api-binding -n task-api

Output:

Name:         task-api-binding
Namespace: task-api
Role:
Kind: Role
Name: task-api-role
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount task-api-sa task-api

Common RBAC Mistakes

SymptomLikely CauseFix
All requests deniedRoleBinding missing or wrong namespaceCheck binding exists in correct namespace
Can access in one namespace but not anotherUsing Role instead of ClusterRoleCreate RoleBinding in each needed namespace
Token not found in podautomountServiceAccountToken: falseSet to true if API access needed
Wrong ServiceAccount usedserviceAccountName not specifiedAdd serviceAccountName to pod spec

The Complete RBAC Pattern

Here's the complete set of resources in a single file for easy deployment. Create task-api-rbac-complete.yaml:

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: task-api-sa
namespace: task-api
automountServiceAccountToken: false
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: task-api-role
namespace: task-api
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: task-api-binding
namespace: task-api
subjects:
- kind: ServiceAccount
name: task-api-sa
namespace: task-api
roleRef:
kind: Role
name: task-api-role
apiGroup: rbac.authorization.k8s.io

Apply everything:

kubectl apply -f task-api-rbac-complete.yaml

Output:

serviceaccount/task-api-sa created
role.rbac.authorization.k8s.io/task-api-role created
rolebinding.rbac.authorization.k8s.io/task-api-binding created

Reflect on Your Skill

Test whether your cloud-security skill generates least-privilege RBAC:

  1. Does your skill set automountServiceAccountToken: false by default? If not, pods get tokens they might not need.

  2. Does your skill avoid wildcards in Role rules? Check for "*" in apiGroups, resources, or verbs.

  3. Does your skill include the kubectl auth can-i verification step? Testing before deploying prevents production issues.

If you found gaps, update your skill with the patterns from this lesson. Your skill should now generate the three-resource pattern (ServiceAccount + Role + RoleBinding) with least privilege defaults.


Try With AI

Use your cloud-security skill to practice RBAC design for different scenarios.

Prompt 1:

Using my cloud-security skill, generate RBAC for a metrics-collector
ServiceAccount that needs to read Pod metrics across all namespaces.
Should I use Role or ClusterRole?

What you're learning: This scenario requires cluster-wide read access, which means ClusterRole + ClusterRoleBinding. Notice how the decision matrix guides you from requirements to the correct resource types. The skill should explain why Role won't work here.

Prompt 2:

My pod is getting "forbidden: User ... cannot get resource pods"
errors. Here's my Role definition. What's wrong?

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: monitoring
rules:
- apiGroups: ["apps"]
resources: ["pods"]
verbs: ["get"]

What you're learning: Debugging RBAC errors by analyzing rule definitions. Pods are in the core API group (empty string ""), not "apps". The skill should identify this mismatch and provide the corrected YAML.

Prompt 3:

Audit this RBAC configuration for security issues:

rules:
- apiGroups: ["*"]
resources: ["secrets"]
verbs: ["*"]

What you're learning: Security review of RBAC rules. This rule grants full access to Secrets across all API groups, which violates least privilege. The skill should identify the wildcards as dangerous and suggest specific, minimal permissions instead.

Security Reminder

Always test RBAC changes with kubectl auth can-i before deploying. A misconfigured RoleBinding can grant more access than intended or block legitimate operations.