Certified Kubernetes Administrator (CKA) #9 RBAC: Role/ClusterRole, RoleBinding, ServiceAccount, kubectl auth can-i

In #8 Certificate Management, we worked through PKI and kubeconfig to settle who gets into the cluster — that is, authentication. Once you issue a user certificate via CSR, that user can prove to the apiserver who they are. But if everyone who gets in can do anything, that isn’t operations. This post tackles the next question — who can do what, that is, authorization.

Kubernetes’ default authorization mode is RBAC (Role-Based Access Control). CKAD touched on RBAC briefly, but CKA goes one level deeper from the operator’s perspective. You need to have these in your hands: binding a ClusterRole with a RoleBinding to scope it to a namespace, the pattern of granting app permissions to a ServiceAccount, and verifying someone else’s permissions on their behalf with auth can-i --as. RBAC also comes up in #25 Troubleshooting 4 as the cause of failures where “a task is blocked due to missing permissions,” so we’ll lock the structure down firmly here.

Authorization after authentication: who can do what #

Every request that reaches the apiserver passes through three gates in turn. First, authentication confirms “who are you,” next authorization decides “are you allowed to do this,” and finally admission control checks “does the content comply with policy.” RBAC takes charge of the second gate.

The way RBAC decides is simple. The default is deny all, and a request only passes when some rule explicitly allows it. In other words, RBAC has no “deny” rules — it’s a model of stacking up allow rules, and if there’s no allow anywhere, the request is denied. Once you’ve burned this additive model into your head, you can answer “why don’t I have permission” immediately with “because there’s no allow rule.”

The four RBAC objects #

RBAC consists of only four kinds of objects. Two define a bundle of permissions, and the other two connect that bundle to whom it’s given.

ObjectRoleScope
RoleA bundle of permission rulesWithin a single namespace
ClusterRoleA bundle of permission rulesWhole cluster / all namespaces / non-namespaced resources
RoleBindingConnects a bundle to a subjectWithin a single namespace
ClusterRoleBindingConnects a bundle to a subjectWhole cluster

The key point is that the objects that define permissions (Role/ClusterRole) and the objects that grant them (RoleBinding/ClusterRoleBinding) are separated. It’s thanks to this separation that a single ClusterRole can be reused by several RoleBindings, each in a different namespace.

rules: apiGroups / resources / verbs #

The substance of a Role and a ClusterRole is the rules array. Each rule line uses three axes to write “what action to allow on which resource.”

  • apiGroups: the API group the resource belongs to. The core group (Pod, Service, ConfigMap, and so on) is written as the empty string "". Deployment and ReplicaSet belong to apps, Job to batch, and Ingress and NetworkPolicy to networking.k8s.io.
  • resources: the plural name of the target resource. Like pods, deployments, services. Subresources such as pods/log or pods/exec are also specified separately here.
  • verbs: the actions to allow. There are get, list, watch, create, update, patch, delete, deletecollection. For read-only, you commonly grant the three get/list/watch together.

To restrict to specific objects only, you can also pin the name with resourceNames. However, resourceNames doesn’t apply to list, watch, or create, so in operations it’s mainly used for narrow permissions like “get/update only the ConfigMap with this name.”

subjects: User / Group / ServiceAccount #

The subjects of a RoleBinding and a ClusterRoleBinding are the principals that receive the permission. There are three kinds.

  • User: a human user. Kubernetes has no User object. As you saw in #8, an external identity like a certificate’s CN is itself the User name.
  • Group: a group of users. It’s determined by the certificate’s O field or a token’s group claim. There are also groups the cluster pre-defines, such as system:masters.
  • ServiceAccount: the identity of an app (Pod). It’s an actual object that belongs to a namespace, and it’s the permission principal a Pod uses when calling the apiserver.

If you remember the difference — User and Group are external identities the cluster doesn’t manage, and only ServiceAccount is an object inside the cluster — you won’t get confused when writing bindings.

Role vs ClusterRole, RoleBinding vs ClusterRoleBinding #

What CKA asks most often is the combination rules of these four objects. The scope a permission reaches changes depending on how you pair a permission bundle (Role/ClusterRole) with a binding (RoleBinding/ClusterRoleBinding).

Permission bundleBindingResulting permission scope
RoleRoleBindingResources within the single namespace where that RoleBinding lives
ClusterRoleClusterRoleBindingAll namespaces + non-namespaced resources (the whole cluster)
ClusterRoleRoleBindingGrants the permissions defined in the ClusterRole, scoped to the single namespace where that RoleBinding lives
RoleClusterRoleBindingNot possible. A Role is tied to a namespace, so it can’t be granted at cluster scope

The third row, the combination of binding a ClusterRole with a RoleBinding, is especially useful in operations. Define a ClusterRole like “permission to read Pods” just once, and have each team point a RoleBinding at that ClusterRole in its own namespace; then each team can read only the Pods in its own namespace. You keep one copy of the permission bundle while narrowing the scope of application down to a namespace.

Non-namespaced resources go through ClusterRole only #

Resources like Node, PersistentVolume, Namespace itself, and CertificateSigningRequest don’t belong to any namespace. Permissions for these cluster-scoped resources can’t be expressed with a Role and must be defined with a ClusterRole. This is why your answer goes wrong if you use a Role on a problem like “create a permission to list nodes.”

Building fast with generators #

Hand-editing RBAC as YAML in the exam wastes time. The fast flow is to build the skeleton with the kubectl create generator, and only when needed pull it out with $do (= --dry-run=client -o yaml) to fix it up.

Role and ClusterRole #

# Namespace Role: read-only on pods
k create role pod-reader \
  --verb=get,list,watch \
  --resource=pods \
  -n dev

# Including a subresource (up to pods/log)
k create role log-reader \
  --verb=get,list \
  --resource=pods,pods/log \
  -n dev

# ClusterRole: cluster scope (all ns / Node, etc.)
k create clusterrole deploy-manager \
  --verb=get,list,watch,create,update,patch,delete \
  --resource=deployments.apps

# ClusterRole that reads nodes
k create clusterrole node-viewer \
  --verb=get,list,watch \
  --resource=nodes

Using the resource.group form like --resource=deployments.apps lets you specify the apiGroup precisely. Core-group resources are written without a group, like pods.

RoleBinding and ClusterRoleBinding #

# A Role to a user (only within that ns)
k create rolebinding pod-reader-bind \
  --role=pod-reader \
  --user=jane \
  -n dev

# A ClusterRole via RoleBinding: manage deploy only within the dev ns
k create rolebinding deploy-mgr-bind \
  --clusterrole=deploy-manager \
  --user=jane \
  -n dev

# A ClusterRole to a ServiceAccount (whole cluster)
k create clusterrolebinding node-viewer-bind \
  --clusterrole=node-viewer \
  --serviceaccount=monitoring:metrics-sa

# A ClusterRole to a whole group (whole cluster)
k create clusterrolebinding dev-admins \
  --clusterrole=admin \
  --group=dev-team

--role points to a Role in the same namespace, and --clusterrole points to a ClusterRole. You choose the subject from among --user, --group, and --serviceaccount=<ns>:<name>. Note that a ServiceAccount must be written in the namespace:name form.

auth can-i: verify permissions #

If you set up RBAC and move on without confirming that the permission actually works, you’ll lose points in the exam. kubectl auth can-i is that verification tool.

# Check my own permission
k auth can-i create deployments -n dev
# answers with yes or no

# Check as if you were a specific user (--as)
k auth can-i get pods --as jane -n dev

# Impersonate the group too
k auth can-i delete nodes --as jane --as-group dev-team

# Check a ServiceAccount's permission
k auth can-i list pods \
  --as=system:serviceaccount:monitoring:metrics-sa -n dev

# See everything that subject can do at a glance
k auth can-i --list --as jane -n dev

--as is the impersonation feature. With admin permissions, you ask the apiserver as if that user had made the request and receive only the RBAC decision in return. Without actually changing your kubeconfig, you can instantly confirm “can jane do this,” which makes it the fastest way to verify the right answer in RBAC work.

When impersonating a ServiceAccount, you use the --as=system:serviceaccount:<namespace>:<name> form. This system:serviceaccount:... string is also the ServiceAccount’s internal User name.

ServiceAccount: granting permissions to an app #

The User/Group so far were for people. But permission is also needed when an app running inside the cluster has to call the apiserver — for example, when a controller watches Pods, or a monitoring app reads node metrics. The identity used here is the ServiceAccount.

# Create a ServiceAccount
k create serviceaccount metrics-sa -n monitoring

# Attach to a Pod (for a deployment, spec.template.spec)
k set serviceaccount deployment/metrics metrics-sa -n monitoring

Once you write the ServiceAccount name in a Pod’s spec.serviceAccountName, a ServiceAccount token is automatically mounted inside that Pod. The app inside the Pod authenticates to the apiserver with this token and can act only within the scope allowed by the Role/ClusterRole bound to that ServiceAccount.

The operating principle is least privilege. Give each app a dedicated ServiceAccount, and bind a Role that allows only the actions that app actually needs. Every namespace has a default ServiceAccount, but if you pile permissions onto it, every Pod in that namespace ends up with the same permissions, so using a dedicated ServiceAccount is safer.

Wiring in the user we created in #8 #

In #8, once you issue a user certificate via CSR, that certificate’s CN becomes the User name. This user is authenticated but has no permissions yet. Only after you attach permissions with RBAC can they get to work.

# Grant dev ns permissions to the user issued with CN=jane in #8
k create role developer \
  --verb=get,list,watch,create,update,patch,delete \
  --resource=pods,deployments.apps,services \
  -n dev

k create rolebinding jane-dev \
  --role=developer \
  --user=jane \
  -n dev

# Verify
k auth can-i create deployments --as jane -n dev   # yes
k auth can-i create deployments --as jane -n prod   # no

These two steps combined — creating an identity with a certificate (#8) and attaching permissions with RBAC (this post) — complete the exam task “grant a specific user permissions in a specific namespace.”

A line on aggregated ClusterRole #

A ClusterRole has an aggregated ClusterRole feature that bundles several ClusterRoles by label and composes them automatically. A ClusterRole that writes a label selector in its aggregationRule automatically absorbs the rules of other ClusterRoles carrying that label, so you can extend default ClusterRoles like admin, edit, and view with permissions for a new resource just by attaching a label.

YAML examples #

The generator is enough, but you need to be able to confirm the structure with your eyes and fix it up. Let’s look at two representative combinations in YAML.

Role + RoleBinding (scoped to a namespace) #

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: dev
  name: pod-reader
rules:
  - apiGroups: [""]            # core group
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: dev
  name: pod-reader-bind
subjects:
  - kind: User
    name: jane              # external identity such as a certificate CN
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Once created, roleRef cannot be modified. To change a binding’s target Role, you have to delete the RoleBinding and recreate it. The difference that a subject’s apiGroup is rbac.authorization.k8s.io for a User/Group but the empty string ("") for a ServiceAccount also trips people up often in the exam.

ClusterRole via RoleBinding (reuse + scope limiting) #

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole         # define once, reuse across many ns
metadata:
  name: deployment-reader
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding         # grant the ClusterRole scoped only to the dev ns
metadata:
  namespace: dev
  name: deploy-reader-bind
subjects:
  - kind: ServiceAccount
    name: ci-bot
    namespace: dev
roleRef:
  kind: ClusterRole       # roleRef.kind is ClusterRole
  name: deployment-reader
  apiGroup: rbac.authorization.k8s.io

Even though roleRef.kind is ClusterRole, because it’s bound with a RoleBinding, the ci-bot ServiceAccount can read only Deployments in the dev namespace. If a RoleBinding in the prod namespace also points at the same ClusterRole, the subject there reads only prod’s Deployments. It’s the pattern of one permission bundle with the scope of application split per namespace.

Exam points #

  • Additive model. RBAC has no deny rules. If there’s not a single allow, it’s denied, and allow rules stack up.
  • Memorize the combination table of the four objects. In particular, distinguish ClusterRole + RoleBinding (reuse + ns scoping) from Role + ClusterRoleBinding being impossible.
  • Non-namespaced resources (Node, PV, Namespace, etc.) can only be granted permissions via ClusterRole. Making them with a Role is wrong.
  • apiGroups. Core resources are "", Deployment is apps, Job is batch, Ingress and NetworkPolicy are networking.k8s.io.
  • Get the generators into your hands. The --verb and --resource of k create role/clusterrole, and the --role/--clusterrole and --user/--group/--serviceaccount of k create rolebinding/clusterrolebinding.
  • Distinguish the --serviceaccount=ns:name form from impersonation’s --as=system:serviceaccount:ns:sa form.
  • Verification is auth can-i. Confirm the right answer right after the task with --as, --as-group, and --list.
  • roleRef is immutable. To change the target, recreate the binding.
  • A subject’s apiGroup. User/Group is rbac.authorization.k8s.io, ServiceAccount is "".

Wrap-up #

What this post locked in:

  • RBAC is authorization after authentication. The apiserver inspects requests in the order authentication → authorization → admission, and RBAC takes the second gate.
  • The four objects. Role/ClusterRole are the permission bundle, and RoleBinding/ClusterRoleBinding connect that bundle to a subject.
  • rules are apiGroups/resources/verbs, and subjects are the three kinds User/Group/ServiceAccount.
  • Combination rules. Binding a ClusterRole with a RoleBinding lets you reuse permissions while scoping to a namespace, and non-namespaced resources are handled only by ClusterRole.
  • Give apps least privilege with a ServiceAccount, and attach permissions to the certificate user from #8 via RBAC to complete the task.
  • The habit of verifying with auth can-i --as protects your score in the exam.

The fundamentals of RBAC are also covered from a multi-tenant angle alongside NetworkPolicy and ResourceQuota in the K8s intermediate track’s #7 RBAC / NetworkPolicy / ResourceQuota, so if you want to see the three policies of permissions, traffic, and resources side by side, it’s worth reading alongside this post.

Next — Workloads 1 #

From #2 up to here, we’ve wrapped up the Cluster Architecture domain. We covered control plane structure, installation, HA, upgrades, etcd backup, certificates, and now this RBAC — the work of standing up and protecting the cluster itself.

From #10 Workloads 1, we move into the workloads that run on top of the cluster. We’ll look deeply at how a Deployment works together with ReplicaSet, how a rolling update proceeds, and how to roll back when a problem arises — all from the operator’s perspective.

X