TT Lab
Get started
Learn Learning paths Courses

CKS — Kubernetes Security Specialist

Narrowing RBAC and Blocking Tokens

Continue in TT Lab

Goal

You find roles with excessive permissions in the cluster yourself, replace them with least-privilege Roles, and check the result by asking the API server. You also close every path by which a ServiceAccount token automatically gets into a Pod.

Why it matters

RBAC decides "who can do what," but it is very hard for a person to verify it by reading. If a single ClusterRole has resources: ["*"] mixed in, that role comes to include even CRDs added in the future, and no one notices. So the real work of hardening starts not with "writing policy well" but with "pulling out a list of what is allowed right now."

The second axis is credentials. A Pod receives a token by default. It receives one even if the application doesn't use the API. If a single Pod is breached, the attacker immediately obtains a cluster credential. automountServiceAccountToken: false is one line, but it greatly reduces the spread in the event of a compromise.

Steps

  1. Create the namespace cks-rbac and create the ServiceAccount report-sa in it.
  2. From all the ClusterRoles in the cluster, pick only the names of those with * in rules[].verbs, sort them alphabetically, and save them to /root/cks-rbac-hardening/wildcard-roles.txt, one per line.
  3. Create the ClusterRole cks-pod-reader. apiGroups is one entry, the core group (an empty string), resources are pods and pods/log, and verbs are the three get, list, and watch. No field may contain *.
  4. From all the ClusterRoles in the cluster, sort only the names of those that have any of escalate, bind, or impersonate in verbs and save them to /root/cks-rbac-hardening/danger-verb-roles.txt.
  5. In the cks-rbac namespace, bind the ClusterRole cks-pod-reader to report-sa with the RoleBinding report-sa-pod-reader. Then save the answers (yes or no) to the two questions below, in order, as two lines in /root/cks-rbac-hardening/can-i.txt. The first line is whether report-sa can list pods in cks-rbac, and the second line is whether report-sa can get secrets in cks-rbac.
  6. Create a Pod report (image nginx:1.27-alpine) in cks-rbac. serviceAccountName is report-sa, and the Pod spec's automountServiceAccountToken is false.
  7. Create a Secret report-sa-token in cks-rbac. The type is kubernetes.io/service-account-token, and the value of the annotation kubernetes.io/service-account.name is report-sa. Then create a Pod token-app (image nginx:1.27-alpine, serviceAccountName report-sa), but have it receive the token through a projected volume. The volume name is api-token, the audience of the serviceAccountToken source is api, expirationSeconds is 3600, and path is token.
  8. Set automountServiceAccountToken: false on the default ServiceAccount of cks-rbac, and save the fact that the default SA can't list pods in this namespace (no) as one line in /root/cks-rbac-hardening/default-sa-can-i.txt.

Notes

A namespace and a dedicated ServiceAccount

Create the namespace cks-rbac and create the ServiceAccount report-sa in it.

Create it with kubectl create serviceaccount. If you don't create the namespace first, creating the SA fails.

Find the wildcard ClusterRoles

From all the ClusterRoles in the cluster, pick only the names of those with * in rules[].verbs, sort them alphabetically, and save them to /root/cks-rbac-hardening/wildcard-roles.txt, one per line.

Scan kubectl get clusterrole -o json with jq and pick those whose rules have * in verbs. Save only the names, one per line.

Write a narrowed ClusterRole

Create the ClusterRole cks-pod-reader. apiGroups is one entry, the core group (an empty string), resources are pods and pods/log, and verbs are the three get, list, and watch. No field may contain *.

You can make a draft with kubectl create clusterrole --resource= --verb=. Don't use a wildcard in any of apiGroups, resources, or verbs.

Pick out roles that have dangerous verbs

From all the ClusterRoles in the cluster, sort only the names of those that have any of escalate, bind, or impersonate in verbs and save them to /root/cks-rbac-hardening/danger-verb-roles.txt.

The targets are ClusterRoles that have any one of the three verbs escalate, bind, and impersonate. Sort and save only the names, the same way as in step 02.

Check the result with auth can-i

In the cks-rbac namespace, bind the ClusterRole cks-pod-reader to report-sa with the RoleBinding report-sa-pod-reader. Then save the answers (yes or no) to the two questions below, in order, as two lines in /root/cks-rbac-hardening/can-i.txt. The first line is whether report-sa can list pods in cks-rbac, and the second line is whether report-sa can get secrets in cks-rbac.

Impersonate the subject in the format --as=system:serviceaccount:<네임스페이스>:<이름> (the placeholders are the namespace and the name). The output is just one line, yes or no, so you can collect it into the file as is.

Turn off automatic token mounting on the Pod

Create a Pod report (image nginx:1.27-alpine) in cks-rbac. serviceAccountName is report-sa, and the Pod spec's automountServiceAccountToken is false.

automountServiceAccountToken is a top-level field of the Pod spec (directly under spec). It is not inside the container.

A manual token Secret and a projected token

Create a Secret report-sa-token in cks-rbac. The type is kubernetes.io/service-account-token, and the value of the annotation kubernetes.io/service-account.name is report-sa. Then create a Pod token-app (image nginx:1.27-alpine, serviceAccountName report-sa), but have it receive the token through a projected volume. The volume name is api-token, the audience of the serviceAccountToken source is api, expirationSeconds is 3600, and path is token.

A manual token Secret has the type kubernetes.io/service-account-token and designates its owner with the kubernetes.io/service-account.name annotation. The counterpart is to put a serviceAccountToken source in the Pod's projected volume and attach an audience and an expiry time.

Neutralize the default ServiceAccount

Set automountServiceAccountToken: false on the default ServiceAccount of cks-rbac, and save the fact that the default SA can't list pods in this namespace (no) as one line in /root/cks-rbac-hardening/default-sa-can-i.txt.

You can finish it in one line with kubectl patch serviceaccount default -n <네임스페이스> -p '...' (the placeholder is the namespace). And check with can-i that the default SA really has no permissions.