RBAC and Kubernetes Security Basics¶
Overview¶
Every kubectl command and every in-cluster API call is authenticated (who are you?) and authorized (are you allowed?). Role-Based Access Control (RBAC) is Kubernetes' primary authorization model: administrators define Roles (permissions) and RoleBindings (who gets them). Without RBAC, any compromised ServiceAccount token or overly broad kubeconfig grants cluster-wide damage potential.
Platform engineers use RBAC to enforce least privilege — developers deploy to their namespace but cannot read Secrets in production; CI pipelines apply manifests but cannot delete nodes; observability agents list Pods but cannot exec into them. This tutorial builds that mental model and hands-on skill set.
This is Tutorial 13 in Module 5: Security & Tooling of the REBASH Academy Kubernetes series. Complete Health Checks, Probes, and Self-Healing first. This tutorial follows our documentation standards.
Prerequisites¶
- Completed Health Checks, Probes, and Self-Healing
- Understanding of namespaces and Deployments
- Cluster where you can create Roles and RoleBindings (minikube, kind, or cloud cluster with admin access)
- Basic familiarity with verbs: get, list, create, update, delete, patch, watch
- Optional:
opensslorkubectl create tokenfor token-based auth testing
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain the Kubernetes authentication and authorization flow for API requests
- Create Roles and ClusterRoles with granular rules on resources and verbs
- Bind Roles to users, groups, and ServiceAccounts via RoleBindings and ClusterRoleBindings
- Scope developer access to specific namespaces using RBAC
- Configure Pod ServiceAccounts and understand default token behavior
- Apply security contexts for non-root containers and read-only filesystems
- Audit RBAC with
kubectl auth can-iand avoid overly permissive bindings
Architecture Diagram¶
RBAC sits in the authorization path after authentication. Bindings connect subjects (users, groups, ServiceAccounts) to roles.
flowchart TB
REQ[API Request]
AUTHN["Authentication<br/>cert / token / OIDC"]
AUTHZ["Authorization<br/>RBAC webhook"]
ADM[Admission Controllers]
ETCD["(etcd")]
REQ --> AUTHN
AUTHN -->|identity| AUTHZ
AUTHZ -->|allowed| ADM
ADM --> ETCD
subgraph RBAC["RBAC Objects"]
ROLE["Role / ClusterRole<br/>rules: verbs + resources"]
BIND["RoleBinding / ClusterRoleBinding"]
SA[ServiceAccount]
ROLE --> BIND
SA --> BIND
end
AUTHZ --> RBAC Theory¶
Authentication vs Authorization¶
| Stage | Question | Mechanisms |
|---|---|---|
| Authentication | Who is making this request? | Client certs, bearer tokens, OIDC (Google, Azure AD, Okta), webhook |
| Authorization | Is this identity allowed? | RBAC (default), ABAC (legacy), webhook, Node authorizer |
| Admission | Is the request valid/safe? | PodSecurity, ResourceQuota, mutating/validating webhooks |
Human operators typically authenticate via kubectl with OIDC or cloud IAM. Pods authenticate as ServiceAccounts — each namespace has a default ServiceAccount unless specified otherwise.
RBAC Core Objects¶
| Object | Scope | Purpose |
|---|---|---|
| Role | Namespace | Permissions within one namespace |
| ClusterRole | Cluster-wide | Permissions across all namespaces or cluster resources |
| RoleBinding | Namespace | Grants Role (or ClusterRole) to subjects in namespace |
| ClusterRoleBinding | Cluster | Grants ClusterRole cluster-wide |
Rules use API groups, resources, verbs, and optional resource names:
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "create", "update", "patch"]
Common Verbs and Resources¶
| Verb | Effect |
|---|---|
get, list, watch | Read access |
create | Create new objects |
update, patch | Modify existing objects |
delete | Remove objects |
* | All verbs (avoid in production roles) |
Cluster-scoped resources (nodes, persistentvolumes, clusterroles) require ClusterRole — Roles cannot grant them.
Built-in ClusterRoles¶
Kubernetes ships aggregate and system roles:
| ClusterRole | Purpose |
|---|---|
view | Read-only access to most namespaced resources |
edit | Read/write namespaced resources (no Roles or Secrets read) |
admin | Full namespace admin including Roles |
cluster-admin | Unrestricted cluster access — platform team only |
Bind edit or custom roles to developers instead of cluster-admin.
ServiceAccounts¶
Every Pod runs with a ServiceAccount (default: default in its namespace). The legacy auto-mounted token allowed any compromised container to call the API. Modern clusters (1.24+) use bound service account tokens with audience and expiration.
spec:
serviceAccountName: payments-api
automountServiceAccountToken: false # disable if app doesn't need API access
Create dedicated ServiceAccounts per application; grant minimal RBAC via RoleBinding.
Least-Privilege Patterns¶
| Persona | Typical permissions |
|---|---|
| Developer (dev ns) | create/get/list/update Deployments, Pods, ConfigMaps |
| Developer (prod ns) | get/list/watch only; deploy via CI |
| CI/CD pipeline | apply manifests in target namespace; no delete on cluster resources |
| Monitoring agent | get/list/watch Pods, nodes, metrics |
| Backup tool | get/list/watch PVs, PVCs, Secrets (scoped) |
Use RoleBinding to a namespace Role for tenant isolation. Reserve ClusterRoleBinding for platform components.
Security Contexts¶
RBAC controls API access; security contexts harden container runtime:
securityContext:
runAsNonRoot: true
runAsUser: 10001
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
Pod-level and container-level contexts combine. Enforce via Pod Security Standards (restricted baseline) in production namespaces.
Aggregated ClusterRoles¶
The view, edit, and admin roles use aggregation — custom roles labeled rbac.authorization.k8s.io/aggregate-to-edit: "true" merge into built-in roles. Platform teams extend defaults without editing system objects.
Hands-on Lab¶
Step 1 – Verify RBAC is enabled and inspect defaults¶
Command:
kubectl create namespace rbac-lab
kubectl auth can-i create deployments --namespace=rbac-lab
kubectl auth can-i delete nodes
kubectl get clusterrole view -o yaml | head -30
kubectl get clusterrolebinding cluster-admin -o yaml | grep -A5 subjects
Explanation: can-i checks current user's permissions. view ClusterRole defines read-only rules aggregated across API groups.
Expected output:
yes
no
kind: ClusterRole
metadata:
name: view
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
Step 2 – Create a developer Role¶
Command:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: rbac-lab
name: developer
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "create", "update"]
EOF
kubectl describe role developer -n rbac-lab
Explanation: This Role allows deployment management and log access but not Secret read or Role modification — typical developer scope.
Expected output:
Name: developer
Labels: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
configmaps [] [] [get list create update]
pods/log [] [] [get list watch]
...
Step 3 – Create ServiceAccount and RoleBinding¶
Command:
kubectl create serviceaccount dev-sa -n rbac-lab
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: dev-sa-binding
namespace: rbac-lab
subjects:
- kind: ServiceAccount
name: dev-sa
namespace: rbac-lab
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io/v1
EOF
kubectl get rolebinding -n rbac-lab
Explanation: RoleBinding links dev-sa to the developer Role. Pods using this ServiceAccount inherit these permissions when calling the API.
Expected output:
Step 4 – Test permissions as ServiceAccount¶
Command:
kubectl auth can-i create deployments \
--as=system:serviceaccount:rbac-lab:dev-sa \
-n rbac-lab
kubectl auth can-i delete secrets \
--as=system:serviceaccount:rbac-lab:dev-sa \
-n rbac-lab
kubectl auth can-i create deployments \
--as=system:serviceaccount:rbac-lab:dev-sa \
-n default
Explanation: ServiceAccount identity format is system:serviceaccount:<namespace>:<name>. Permissions do not cross namespaces unless ClusterRoleBinding grants them.
Expected output:
Step 5 – Deploy Pod with ServiceAccount and security context¶
Command:
cat <<'EOF' | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-web
namespace: rbac-lab
spec:
replicas: 1
selector:
matchLabels:
app: secure-web
template:
metadata:
labels:
app: secure-web
spec:
serviceAccountName: dev-sa
automountServiceAccountToken: true
securityContext:
runAsNonRoot: true
runAsUser: 101
fsGroup: 101
containers:
- name: nginx
image: nginx:1.25-alpine
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: cache
mountPath: /var/cache/nginx
- name: run
mountPath: /var/run
volumes:
- name: cache
emptyDir: {}
- name: run
emptyDir: {}
EOF
kubectl get pods -n rbac-lab -l app=secure-web
kubectl exec -n rbac-lab deploy/secure-web -- id
Explanation: nginx runs as UID 101 with read-only root filesystem — writable paths use emptyDir volumes. Combined with minimal RBAC, compromise impact is reduced.
Expected output:
Step 6 – Create read-only ClusterRole for observability¶
Command:
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log", "namespaces"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
namespace: rbac-lab
subjects:
- kind: ServiceAccount
name: dev-sa
namespace: rbac-lab
roleRef:
kind: ClusterRole
name: pod-reader
apiGroup: rbac.authorization.k8s.io/v1
EOF
kubectl auth can-i list pods --all-namespaces \
--as=system:serviceaccount:rbac-lab:dev-sa
Explanation: A ClusterRole can be bound with a namespaced RoleBinding — permissions apply only in rbac-lab despite ClusterRole scope.
Expected output:
Step 7 – Audit and clean up¶
Command:
kubectl auth can-i list pods \
--as=system:serviceaccount:rbac-lab:dev-sa \
-n rbac-lab
kubectl get role,rolebinding,sa -n rbac-lab
kubectl delete namespace rbac-lab --wait=false
Explanation: Regular RBAC audits (can-i, reviewing bindings) catch permission creep after incidents or onboarding changes.
Commands & Code¶
| Command | Description | Example |
|---|---|---|
kubectl auth can-i | Check permission | kubectl auth can-i delete pods -n prod |
kubectl auth can-i --list | List all permissions | kubectl auth can-i --list -n dev |
kubectl create role | Imperative role creation | kubectl create role pod-reader --verb=get,list --resource=pods |
kubectl create rolebinding | Bind role to subject | kubectl create rolebinding ... --role=... --serviceaccount=... |
kubectl get clusterrolebindings | List cluster-wide bindings | kubectl get clusterrolebindings |
CI/CD deploy Role (namespace-scoped)¶
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cicd-deployer
namespace: production
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "configmaps"]
verbs: ["get", "list", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: github-actions-deploy
namespace: production
subjects:
- kind: User
name: github-actions-prod
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: cicd-deployer
apiGroup: rbac.authorization.k8s.io
Common Mistakes¶
Granting cluster-admin to developers
Full cluster access bypasses namespace isolation. Use namespace-scoped Roles; escalate through break-glass procedures only.
Using default ServiceAccount for all Pods
Every Pod in a namespace shares the same default identity. Create per-app ServiceAccounts with minimal bindings.
Wildcard verbs or resources in Roles
verbs: ["*"] on resources: ["*"] is equivalent to admin. Enumerate only required permissions.
Forgetting cross-namespace Secret access
ClusterRoleBindings to view or custom roles may expose Secrets. Kubernetes hides Secret content from view — verify custom roles do not grant secrets get/list unnecessarily.
Best Practices¶
Use RBAC for humans and machines separately
OIDC groups for engineers; dedicated ServiceAccounts for CI, operators, and agents — each with distinct Roles.
Automate RBAC in Git
Store Roles and RoleBindings in version control. Review permission changes like application code.
Disable automount when not needed
Set automountServiceAccountToken: false for workloads that never call the Kubernetes API — reduces token exfiltration risk.
Combine RBAC with Pod Security Standards
RBAC prevents unauthorized API changes; Pod Security restricts privileged containers at admission — defense in depth.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
Forbidden on kubectl | Missing Role or RoleBinding | kubectl auth can-i --list; add binding |
| Pod cannot access API | SA lacks permissions | Check RoleBinding; verify SA name in Pod spec |
| Works as admin, fails as SA | Binding wrong namespace/subject | Verify subject name and namespace match |
| Token not mounted | automount disabled or projected token | Check Pod spec; use projected volume if needed |
| Permission denied cross-namespace | Role is namespace-scoped | Use ClusterRole + RoleBinding or separate Role per ns |
| Security context prevents start | runAsUser conflicts with image | Check image USER; adjust UID or use compatible image |
Summary¶
- Kubernetes authentication identifies callers; RBAC authorization grants or denies API actions
- Roles and ClusterRoles define permissions; RoleBindings and ClusterRoleBindings assign them to users, groups, and ServiceAccounts
- Least privilege means namespace-scoped roles for developers, dedicated ServiceAccounts per workload, and no unnecessary
cluster-admin - Security contexts enforce non-root execution, dropped capabilities, and read-only filesystems at runtime
- Audit with
kubectl auth can-iand treat RBAC manifests as version-controlled infrastructure - RBAC is necessary but not sufficient — pair with Pod Security, network policies, and secrets management
Interview Questions¶
- What is the difference between a Role and a ClusterRole?
- Explain how RoleBinding connects subjects to permissions.
- How does a Pod authenticate to the Kubernetes API?
- What changed about ServiceAccount tokens in Kubernetes 1.24+?
- Why should developers not receive cluster-admin in production clusters?
- What is the difference between the built-in
view,edit, andadminClusterRoles? - How do you test whether a ServiceAccount can perform a specific action?
- What is a security context, and name three fields that improve container security?
- Can a ClusterRole be bound to permissions in a single namespace? How?
- What is the difference between RBAC authorization and admission control?
Sample Answers (Questions 1, 3, and 9)
Q1 — Role vs ClusterRole: A Role defines permissions within a single namespace — it can only grant access to namespaced resources in that namespace. A ClusterRole is cluster-scoped: it can grant access to cluster resources (nodes, PVs, ClusterRoles) and can also grant access to namespaced resources across all namespaces. Use Roles for tenant isolation; ClusterRoles for platform components or when binding cluster-wide read access.
Q3 — Pod API authentication: Pods run as a ServiceAccount (explicit or default). When automountServiceAccountToken is true, the kubelet mounts a JWT token the Pod can present to the API server. The API server authenticates the token as system:serviceaccount:<ns>:<name>, then RBAC checks whether that identity has permission for the requested verb and resource.
Q9 — ClusterRole in one namespace: Yes. Create a ClusterRole with desired rules, then create a RoleBinding (not ClusterRoleBinding) in the target namespace. Reference the ClusterRole in roleRef. The subject receives only the permissions defined in the ClusterRole, scoped to the RoleBinding's namespace.
Related Tutorials¶
- Kubernetes – Category Overview
- Health Checks, Probes, and Self-Healing (previous — Module 4)
- Troubleshooting Kubernetes Workloads (next in Module 5)
- Namespaces and Resource Management
- Networking – Reverse Proxy and Ingress