CKS — Kubernetes Security Specialist
Narrowing RBAC and Blocking Tokens
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
- Create the namespace
cks-rbacand create the ServiceAccountreport-sain it. - From all the ClusterRoles in the cluster, pick only the names of those with
*inrules[].verbs, sort them alphabetically, and save them to/root/cks-rbac-hardening/wildcard-roles.txt, one per line. - Create the ClusterRole
cks-pod-reader. apiGroups is one entry, the core group (an empty string), resources arepodsandpods/log, and verbs are the threeget,list, andwatch. No field may contain*. - From all the ClusterRoles in the cluster, sort only the names of those that have any of
escalate,bind, orimpersonatein verbs and save them to/root/cks-rbac-hardening/danger-verb-roles.txt. - In the
cks-rbacnamespace, bind the ClusterRolecks-pod-readertoreport-sawith the RoleBindingreport-sa-pod-reader. Then save the answers (yesorno) to the two questions below, in order, as two lines in/root/cks-rbac-hardening/can-i.txt. The first line is whetherreport-sacan list pods incks-rbac, and the second line is whetherreport-sacan get secrets incks-rbac. - Create a Pod
report(imagenginx:1.27-alpine) incks-rbac.serviceAccountNameisreport-sa, and the Pod spec'sautomountServiceAccountTokenisfalse. - Create a Secret
report-sa-tokenincks-rbac. The type iskubernetes.io/service-account-token, and the value of the annotationkubernetes.io/service-account.nameisreport-sa. Then create a Podtoken-app(imagenginx:1.27-alpine, serviceAccountNamereport-sa), but have it receive the token through a projected volume. The volume name isapi-token, theaudienceof theserviceAccountTokensource isapi,expirationSecondsis3600, andpathistoken. - Set
automountServiceAccountToken: falseon thedefaultServiceAccount ofcks-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
- Wildcard audit example:
kubectl get clusterrole -o json | jq -r '.items[] | select(...) | .metadata.name' | sort kubectl auth can-i list pods -n cks-rbac --as=system:serviceaccount:cks-rbac:report-sakubectl create clusterrole cks-pod-reader --verb=get,list,watch --resource=pods,pods/log- Common mistake 1: putting
automountServiceAccountTokeninside the container spec. It goes directly under the Pod spec. - Common mistake 2: if you leave out the ServiceAccount's
namespacein a RoleBinding's subjects, the binding has no effect at all.
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.