Asterrr's Handbook

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 wantRules objectBinding
Access in one namespaceRole in that namespaceRoleBinding in that namespace
The same rule set in several namespaces, defined onceClusterRoleOne RoleBinding per namespace
Access in every namespace, including future onesClusterRoleClusterRoleBinding
Cluster-scoped resources (nodes, PVs, namespaces, CRDs)ClusterRoleClusterRoleBinding
A Role bound cluster-wideNot possibleA 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, edit or admin per namespace.
  • roleRef is 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 in apps, Jobs in batch, Ingress and NetworkPolicy in networking.k8s.io. Run kubectl api-resources -o wide for group and verbs.
  • Subresources are separate resources: pods/log for logs, pods/exec for exec (verb create), deployments/scale for scaling, pods/portforward for port-forward.
  • resourceNames can't restrict list, watch or create, because those requests carry no object name.
  • Verbs are get, list, watch, create, update, patch, delete, deletecollection, plus special ones such as impersonate, bind and escalate.

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-ops

ServiceAccount 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

SubjectExists as an object?How it authenticates
UserNo. Kubernetes has no User resourceClient certificate CN, OIDC token claim, or another authenticator
GroupNoCertificate O fields, OIDC groups claim. Built-ins: system:authenticated, system:serviceaccounts, system:serviceaccounts:<ns>
ServiceAccountYes, namespacedShort-lived projected token mounted into Pods; username system:serviceaccount:<ns>:<name>
  • k create sa ci-runner -n orbit creates one. Set it on a Pod with spec.serviceAccountName. Every namespace has a default ServiceAccount that Pods use when you don't set one.
  • k create token ci-runner -n orbit --duration=1h issues a time-bound token for testing or for a kubeconfig.
  • Set automountServiceAccountToken: false on the ServiceAccount or the Pod for workloads that never call the API.
  • A new human user usually comes from a client certificate. Create a CertificateSigningRequest with signerName: kubernetes.io/kube-apiserver-client, then k 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

ClusterRoleGrants
cluster-adminEverything. With a ClusterRoleBinding, every namespace; with a RoleBinding, everything in that one namespace
adminRead and write most namespaced objects, including Roles and RoleBindings in the namespace. No quota or namespace edits
editRead and write most namespaced objects, but not Roles or RoleBindings
viewRead-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

Scenario
Five team namespaces each need a ServiceAccount named deployer that can create and update Deployments, but only in its own namespace. You want the rule set defined once. What is the least-effort correct setup?
Scenario
A user bound to the built-in view ClusterRole in namespace vault-ui reports that `kubectl get secrets -n vault-ui` returns Forbidden, while `kubectl get pods` works. What explains this?
Scenario
You created a RoleBinding named audit-read that points at the Role pod-reader. The task now says it must use the ClusterRole view. Running `kubectl edit rolebinding audit-read` and changing roleRef is rejected. What do you do?

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.

Further reading

On this page