RBAC
Roles and ClusterRoles, RoleBindings and ClusterRoleBindings, users, groups and ServiceAccounts as subjects, aggregated ClusterRoles, and proving access with kubectl auth can-i.
Exam tasks: 1.1 (manage role-based access control)
The decision: which rule object (Role or ClusterRole) and which binding (RoleBinding or ClusterRoleBinding) give this subject exactly the access the task asks for, in exactly the namespaces it names?
The four objects
| You want | Rules object | Binding |
|---|---|---|
| Access in one namespace | Role in that namespace | RoleBinding in that namespace |
| The same rule set in several namespaces, defined once | ClusterRole | One RoleBinding per namespace |
| Access in every namespace, including future ones | ClusterRole | ClusterRoleBinding |
| Cluster-scoped resources (nodes, PVs, namespaces, CRDs) | ClusterRole | ClusterRoleBinding |
| A Role bound cluster-wide | Not possible | A ClusterRoleBinding can only reference a ClusterRole |
- RBAC is additive only. There are no deny rules; access is the union of every binding that matches.
- A RoleBinding that references a ClusterRole grants those rules only in the binding's namespace. This is the
normal way to reuse
view,editoradminper namespace. roleRefis immutable. To point a binding at a different role, delete and recreate it.
Exam signal
"In namespace X only" means a RoleBinding, even when the task tells you to create a ClusterRole. "Across all namespaces" or anything touching nodes, PersistentVolumes or namespaces themselves means a ClusterRoleBinding.
Writing rules
A rule combines apiGroups, resources, verbs and optionally resourceNames.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: release-reader
namespace: orbit
rules:
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""] # core group: pods, services, configmaps, secrets
resources: ["pods", "pods/log"]
verbs: ["get", "list"]
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["orbit-flags"] # one named object only
verbs: ["get", "update"]- The core group is the empty string
"". Deployments, StatefulSets and DaemonSets are inapps, Jobs inbatch, Ingress and NetworkPolicy innetworking.k8s.io. Runkubectl api-resources -o widefor group and verbs. - Subresources are separate resources:
pods/logfor logs,pods/execfor exec (verbcreate),deployments/scalefor scaling,pods/portforwardfor port-forward. resourceNamescan't restrictlist,watchorcreate, because those requests carry no object name.- Verbs are
get,list,watch,create,update,patch,delete,deletecollection, plus special ones such asimpersonate,bindandescalate.
The imperative way is faster and gets the plurals right:
k create role release-reader -n orbit --verb=get,list,watch --resource=deployments.apps,pods
k create clusterrole node-peeker --verb=get,list --resource=nodes
k create rolebinding rr-ci -n orbit --role=release-reader --serviceaccount=orbit:ci-runner
k create rolebinding view-maya -n orbit --clusterrole=view --user=maya
k create clusterrolebinding peek-ops --clusterrole=node-peeker --group=night-opsServiceAccount flag format
--serviceaccount takes namespace:name. Writing --serviceaccount=ci-runner fails, and binding the right name
from the wrong namespace silently grants nothing. In YAML the subject needs kind: ServiceAccount, name and
namespace; leaving out namespace is the classic mistake.
Subjects
| Subject | Exists as an object? | How it authenticates |
|---|---|---|
| User | No. Kubernetes has no User resource | Client certificate CN, OIDC token claim, or another authenticator |
| Group | No | Certificate O fields, OIDC groups claim. Built-ins: system:authenticated, system:serviceaccounts, system:serviceaccounts:<ns> |
| ServiceAccount | Yes, namespaced | Short-lived projected token mounted into Pods; username system:serviceaccount:<ns>:<name> |
k create sa ci-runner -n orbitcreates one. Set it on a Pod withspec.serviceAccountName. Every namespace has adefaultServiceAccount that Pods use when you don't set one.k create token ci-runner -n orbit --duration=1hissues a time-bound token for testing or for a kubeconfig.- Set
automountServiceAccountToken: falseon the ServiceAccount or the Pod for workloads that never call the API. - A new human user usually comes from a client certificate. Create a
CertificateSigningRequestwithsignerName: kubernetes.io/kube-apiserver-client, thenk certificate approve <name>and read the signed certificate from.status.certificate. The CN becomes the username and each O becomes a group.
Legacy: use kubectl create token or projected tokens instead
Older material says every ServiceAccount gets a long-lived token Secret automatically. Since v1.24 that no longer
happens, and unused auto-generated legacy tokens are now cleaned up. If something truly needs a non-expiring token,
create a Secret of type kubernetes.io/service-account-token with the kubernetes.io/service-account.name
annotation, and prefer short-lived tokens everywhere else.
Default and aggregated ClusterRoles
| ClusterRole | Grants |
|---|---|
cluster-admin | Everything. With a ClusterRoleBinding, every namespace; with a RoleBinding, everything in that one namespace |
admin | Read and write most namespaced objects, including Roles and RoleBindings in the namespace. No quota or namespace edits |
edit | Read and write most namespaced objects, but not Roles or RoleBindings |
view | Read-only on most namespaced objects. Excludes Secrets |
admin, edit and view are aggregated: the control plane fills their rules from every ClusterRole carrying
a matching label. To let view holders read a custom resource, label a small ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: telescopes-view
labels:
rbac.authorization.k8s.io/aggregate-to-view: "true"
rules:
- apiGroups: ["astro.example.io"]
resources: ["telescopes"]
verbs: ["get", "list", "watch"]You can build your own aggregated role with aggregationRule.clusterRoleSelectors. Leave its rules empty; the
controller overwrites them.
Proving it with auth can-i
k auth can-i list deployments -n orbit --as=system:serviceaccount:orbit:ci-runner # yes
k auth can-i delete pods -n orbit --as=maya # no
k auth can-i get nodes --as=jo --as-group=night-ops # cluster-scoped
k auth can-i --list -n orbit --as=system:serviceaccount:orbit:ci-runner
k auth can-i create pods --subresource=exec -n orbit --as=maya # subresources
k auth whoami # who am I right now?can-i get pods/log would ask about a Pod named log. Use --subresource for subresources.
Exam signal
Finish every RBAC task with auth can-i for one verb that should work and one that shouldn't. It's what
the grader does, and it catches wrong namespaces and plural typos (deployment vs deployments) in seconds.
Admin kubeconfig and system:masters
The system:masters group bypasses RBAC entirely, so can-i as that identity always says yes. On kubeadm
clusters since v1.29, admin.conf is bound through a ClusterRoleBinding to cluster-admin instead, and only
super-admin.conf sits in system:masters. Test with --as, never by looking at what your own admin context can do.
Scenarios
A ClusterRole defines the rules once, and a RoleBinding scopes the grant to its own namespace. A ClusterRoleBinding would let every deployer change Deployments in all namespaces. A ClusterRoleBinding can't reference a Role, and five Roles defeats the define-once requirement.
The view role leaves out Secrets on purpose, because reading Secrets can expose ServiceAccount tokens and lead to
privilege escalation. Secrets are namespaced, so a Role and RoleBinding in vault-ui fix it. A ClusterRoleBinding
would grant much more than asked, and impersonate is unrelated.
roleRef is immutable on RoleBindings and ClusterRoleBindings, so every in-place change, patch included, is
refused. Recreate the binding. A Role named view is a different object from the ClusterRole and would only grant
whatever rules you put in it.
Drill
kubectl config use-context cka-lab1
In namespace harbor-ops (create it if missing), create a ServiceAccount tally. It must be able to get, list
and watch Pods and read Pod logs, and to scale Deployments, in harbor-ops only. It must not be able to delete
Pods. Use a Role named tally-role and a RoleBinding named tally-bind.
k create ns harbor-ops
k create sa tally -n harbor-ops
# generate the first rule, then add the scale rule by hand
k create role tally-role -n harbor-ops --dry-run=client -o yaml \
--verb=get,list,watch --resource=pods,pods/log > /tmp/role.yamlEdit /tmp/role.yaml so it ends up like this, then apply it:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: tally-role
namespace: harbor-ops
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "deployments/scale"]
verbs: ["get", "update", "patch"]k apply -f /tmp/role.yaml
k create rolebinding tally-bind -n harbor-ops --role=tally-role --serviceaccount=harbor-ops:tallyVerify:
SA=system:serviceaccount:harbor-ops:tally
k auth can-i get pods --subresource=log -n harbor-ops --as=$SA # yes
k auth can-i patch deployments --subresource=scale -n harbor-ops --as=$SA # yes
k auth can-i delete pods -n harbor-ops --as=$SA # no
k auth can-i list pods -n default --as=$SA # noFurther reading
Domain 1 · Cluster architecture, installation and configuration
25% of the exam. Granting access with RBAC, building, upgrading and backing up kubeadm clusters, HA control planes, Helm and Kustomize, plugin interfaces, CRDs and operators.
Installing a cluster with kubeadm
Preparing nodes (swap, IP forwarding, containerd and the cgroup driver, pkgs.k8s.io packages), kubeadm init and its config file, installing a CNI, joining workers and regenerating join commands.