Kubernetes Course
RBAC Introduction
Network Policies protect the data plane — what traffic flows between Pods. RBAC protects the control plane — who can create, read, update, and delete Kubernetes objects. A cluster with no RBAC is a cluster where any compromised workload can delete your entire production deployment in seconds.
What RBAC Controls
Role-Based Access Control (RBAC) is the Kubernetes authorisation system. It answers one question for every API request: "Is this subject allowed to perform this verb on this resource in this namespace?" Every kubectl command, every in-cluster API call from a Pod, every request from your CI/CD pipeline — all go through RBAC before the API server acts on them.
RBAC is built from four object types. Roles and ClusterRoles define a set of permissions. RoleBindings and ClusterRoleBindings grant those permissions to subjects (users, groups, or service accounts). The separation between "what's allowed" (Role) and "who gets it" (RoleBinding) is deliberate — it lets you reuse the same permission set for many subjects.
Role / RoleBinding
Namespace-scoped. A Role grants permissions on resources within one specific namespace. A RoleBinding applies a Role (or ClusterRole) to subjects within that namespace.
ClusterRole / ClusterRoleBinding
Cluster-scoped. A ClusterRole grants permissions on cluster-wide resources (nodes, PVs, namespaces) or across all namespaces. A ClusterRoleBinding applies it cluster-wide.
⚠️ RBAC is deny-by-default
Unlike network policies (which are allow-by-default unless a policy exists), RBAC is deny-by-default for everything. A service account with no RoleBindings cannot do anything. Even a human user authenticated via a certificate has no permissions unless explicitly granted. You build up from zero — no permissions until granted.
The Three Subjects
Every RBAC RoleBinding grants permissions to one or more subjects. There are three kinds of subjects:
| Subject kind | What it represents | Typical use |
|---|---|---|
| User | A human authenticated via a certificate, OIDC token, or webhook. Kubernetes has no built-in user store — identity comes from external providers. | Individual engineers, admins, auditors |
| Group | A named collection of users. Groups come from the identity provider (OIDC groups, certificate O= field). Grant permissions to teams rather than individuals. | Engineering teams, ops teams, read-only auditors |
| ServiceAccount | A namespaced Kubernetes object representing an application identity. Pods automatically get a token for their ServiceAccount. The primary subject type for in-cluster permissions. | CI/CD pipelines, operators, applications that call the Kubernetes API |
Verbs and Resources
Every RBAC rule specifies which verbs are allowed on which resources. Verbs map directly to HTTP methods on the Kubernetes API. Resources are the Kubernetes object types. Rules can be scoped further by resourceNames to allow operations on specific named objects only.
| Verb | What it allows | kubectl equivalent |
|---|---|---|
| get | Read a single named object | kubectl get pod my-pod |
| list | List all objects of a type | kubectl get pods |
| watch | Stream changes to objects in real time | kubectl get pods -w |
| create | Create a new object | kubectl apply (new resource) |
| update | Replace an entire object | kubectl apply (existing resource) |
| patch | Partially update an object | kubectl patch |
| delete | Delete an object | kubectl delete pod my-pod |
| exec / log / portforward | Subresource operations on Pods | kubectl exec, kubectl logs, kubectl port-forward |
Writing Your First Role
The scenario: Your company has a shared production cluster. The payments team should be able to view Pods, Services, and Deployments in their namespace — for day-to-day observability — but must not be able to create, update, or delete anything. An accidental kubectl delete deployment in production should be impossible for regular team members.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # Role: namespace-scoped permissions
metadata:
name: payments-viewer # Descriptive name — what this role allows
namespace: payments # Only grants permissions in the payments namespace
labels:
team: payments
rules: # rules: list of permission grants
- apiGroups:
- "" # "" = core API group (Pods, Services, ConfigMaps, Secrets, etc.)
resources:
- pods # Which resource types this rule applies to
- services
- endpoints
- configmaps
verbs:
- get # Allowed operations: read-only set
- list
- watch
- apiGroups:
- apps # apps API group: Deployments, ReplicaSets, StatefulSets
resources:
- deployments
- replicasets
- statefulsets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods/log # Subresource: allows kubectl logs
verbs:
- get
- list
- apiGroups:
- ""
resources:
- pods/exec # Subresource: allows kubectl exec
verbs:
- create # exec uses 'create' verb despite being read-like
# Granting exec is a significant privilege — it gives shell access
$ kubectl apply -f payments-viewer-role.yaml role.rbac.authorization.k8s.io/payments-viewer created $ kubectl describe role payments-viewer -n payments Name: payments-viewer Namespace: payments Labels: team=payments PolicyRule: Resources Non-Resource URLs Resource Names Verbs --------- ----------------- -------------- ----- pods [] [] [get list watch] services [] [] [get list watch] endpoints [] [] [get list watch] configmaps [] [] [get list watch] deployments.apps [] [] [get list watch] replicasets.apps [] [] [get list watch] statefulsets.apps [] [] [get list watch] pods/log [] [] [get list] pods/exec [] [] [create]
What just happened?
apiGroups: "" — The empty string represents the core API group, which contains Pods, Services, ConfigMaps, Secrets, Namespaces, PVCs, etc. Whenever you want permissions on these fundamental resources, use apiGroups: [""]. For resources in other groups like Deployments (apps), Ingresses (networking.k8s.io), or CronJobs (batch), use the appropriate group name.
Subresources — pods/log and pods/exec — Kubernetes treats some operations as subresources rather than verbs on the main resource. kubectl logs requires the pods/log subresource with get verb. kubectl exec requires pods/exec with create verb. Think carefully about pods/exec — it gives interactive shell access to running containers, which means access to any secrets mounted as env vars or files.
What's NOT in this role — There's no create, update, patch, or delete verb anywhere. Team members with this role can observe everything in the namespace but cannot change anything. They also can't read Secrets (not listed in resources) — important for keeping credentials out of reach.
Creating a RoleBinding
A Role by itself does nothing — it's a permission template. A RoleBinding connects a Role to subjects. You can bind the same Role to multiple subjects, and you can bind multiple Roles to the same subject through multiple RoleBindings.
The scenario: Grant the payments-viewer role to an individual engineer, to the whole payments engineering team (as a group from your OIDC provider), and to a CI/CD service account used for deployment verification.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding # RoleBinding: assigns a Role to subjects in a namespace
metadata:
name: payments-viewer-binding
namespace: payments # This binding only applies within the payments namespace
subjects: # Who gets the permissions
- kind: User # Individual human user
name: alice@company.com # Name must match exactly what appears in their auth token
apiGroup: rbac.authorization.k8s.io
- kind: Group # A group from the identity provider (OIDC, LDAP)
name: payments-engineers # All members of this group get the permissions
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount # A Kubernetes service account (for automated systems)
name: deploy-verifier # ServiceAccount name
namespace: ci-cd # ServiceAccount's namespace — MUST be specified for ServiceAccounts
# A ServiceAccount in a different namespace can be granted access here
roleRef: # Which Role to grant
apiGroup: rbac.authorization.k8s.io
kind: Role # Role (namespace-scoped) or ClusterRole (cluster-scoped)
name: payments-viewer # Must match an existing Role in this namespace
$ kubectl apply -f payments-viewer-binding.yaml rolebinding.rbac.authorization.k8s.io/payments-viewer-binding created $ kubectl describe rolebinding payments-viewer-binding -n payments Name: payments-viewer-binding Namespace: payments Labels: <none> Role: Kind: Role Name: payments-viewer Subjects: Kind Name Namespace ---- ---- --------- User alice@company.com Group payments-engineers ServiceAccount deploy-verifier ci-cd
What just happened?
namespace on ServiceAccount is required — When binding a ServiceAccount that lives in a different namespace (ci-cd) than the RoleBinding itself (payments), the namespace field is mandatory. Without it, the API server doesn't know which namespace's ServiceAccount you mean. Omitting it is a silent error — the binding exists but never matches any subject.
roleRef is immutable — Once a RoleBinding is created, the roleRef cannot be changed. If you need to change the referenced Role, you must delete and recreate the RoleBinding. The subjects list can be updated freely. This immutability prevents privilege escalation through binding modification.
Binding a ClusterRole via RoleBinding — You can bind a ClusterRole through a RoleBinding (namespace-scoped). This is useful for reusing a ClusterRole definition across namespaces without granting cluster-wide access. The ClusterRole's permissions are scoped to the namespace of the RoleBinding. This is how the built-in admin and view ClusterRoles are used — one ClusterRole definition, applied per-namespace via namespace-scoped RoleBindings.
Checking Permissions: auth can-i
Before and after setting up RBAC, you want to verify that the permissions are exactly what you intended. kubectl auth can-i is the authorisation test command — it asks the RBAC system directly whether an action is permitted.
kubectl auth can-i list pods -n payments
# Test your OWN permissions — am I allowed to list pods in the payments namespace?
# Returns "yes" or "no" — simple and unambiguous
kubectl auth can-i list pods -n payments --as alice@company.com
# --as: impersonate another user to check their permissions
# Requires the impersonation privilege itself (cluster-admins have it)
# Essential for verifying permissions without asking the user to test themselves
kubectl auth can-i create deployments -n payments --as=system:serviceaccount:ci-cd:deploy-verifier
# Check a service account's permissions
# Format: system:serviceaccount:[namespace]:[name]
# This tests what the deploy-verifier service account in the ci-cd namespace can do
kubectl auth can-i delete secrets -n payments --as alice@company.com
# Verify that Alice CANNOT delete secrets — confirm the least-privilege is working
kubectl auth can-i "*" "*" -n payments
# Can the current user do EVERYTHING in this namespace?
# Useful for verifying cluster-admin access
kubectl auth can-i --list -n payments --as alice@company.com
# --list: show ALL permissions the user has in this namespace
# Returns a full table of resource/verb combinations — the complete permission picture
$ kubectl auth can-i list pods -n payments --as alice@company.com yes $ kubectl auth can-i delete pods -n payments --as alice@company.com no $ kubectl auth can-i create deployments -n payments --as alice@company.com no $ kubectl auth can-i create pods/exec -n payments --as alice@company.com yes ← exec is allowed (we included it in the role) $ kubectl auth can-i delete secrets -n payments --as alice@company.com no ← secrets not in the role — confirmed ✓ $ kubectl auth can-i --list -n payments --as alice@company.com Resources Non-Resource URLs Resource Names Verbs pods [] [] [get list watch] pods/exec [] [] [create] pods/log [] [] [get list] services [] [] [get list watch] deployments.apps [] [] [get list watch] ... selfsubjectreviews.authentication.k8s.io [] [] [create]
What just happened?
kubectl auth can-i is your RBAC testing tool — This command queries the authorisation API directly — it doesn't actually try to perform the action. It's safe to run in production to verify permissions without triggering any side effects. Make it a habit: after applying any RBAC configuration, verify with can-i from the perspective of the subject.
--as impersonation — The --as flag is one of the most powerful debugging tools in Kubernetes. You can check permissions from any user's or service account's perspective without needing their credentials. This is essential for RBAC audits: verify that each role grants exactly the right set of permissions, no more, no less.
selfsubjectreviews — The --list output includes selfsubjectreviews — this is always granted to all authenticated users and lets them query their own permissions. It's not a security concern.
RBAC Architecture: The Four Objects
Here's how the four RBAC objects relate to each other in practice:
RBAC Object Relationships
rules:
- resources: [pods]
verbs: [get, list]
subjects: [alice, payments-team]
roleRef: payments-viewer
rules:
- resources: [nodes, pvs]
verbs: [get, list]
subjects: [ops-team]
roleRef: cluster-node-reader
Teacher's Note: The three RBAC mistakes that create security incidents
1. Granting cluster-admin to service accounts. I've seen CI/CD pipelines with cluster-admin permissions "because it was easier." A compromised pipeline can then delete namespaces, exfiltrate Secrets across the entire cluster, and modify RBAC policies to escalate further. Service accounts should have the minimum permissions needed for their specific job — usually create/update on a handful of resource types in specific namespaces.
2. Granting pods/exec in production. Shell access to a running container means access to every secret mounted in that container. If you need exec access for debugging, create a temporary RoleBinding, do the debugging, then delete the binding. Don't leave exec permissions permanently granted.
3. Forgetting about the default service account. Every Pod automatically uses the default ServiceAccount in its namespace unless you specify otherwise. If you grant permissions to the default ServiceAccount, every Pod in that namespace inherits those permissions — including your application Pods. Always create dedicated ServiceAccounts for workloads that need API access, and leave the default ServiceAccount with no permissions.
Practice Questions
1. You have created a Role named payments-viewer in the payments namespace. What Kubernetes object do you create to grant those permissions to the user alice@company.com?
2. Write the kubectl command to check whether the user alice@company.com is allowed to list Pods in the payments namespace.
3. To allow a user to run kubectl exec on Pods in a namespace, what resource and verb combination must be included in their Role?
Quiz
1. A brand new ServiceAccount is created with no RoleBindings. What can it do in the cluster?
2. You want all teams to use the same "developer" permission set but scoped to their own namespace. You define it once as a ClusterRole. Can a namespace-scoped RoleBinding reference a ClusterRole?
3. You need to change an existing RoleBinding to reference a different Role. What do you do?
Up Next · Lesson 38
Roles and RoleBindings
Production RBAC patterns — the built-in ClusterRoles, aggregated roles, multi-team namespace access, and the CI/CD service account patterns used by real engineering organisations.