KCSA — Kubernetes Security Associate
Auditing Control Plane Hardening
Goal
List the dangerous settings of the control plane components, write the etcd encryption-at-rest configuration yourself,
measure namespace isolation and the scope of anonymous access with kubectl auth can-i to confirm them,
and then write a hardening report that gathers that evidence.
Why it matters
A security review earns trust not when it says "it is better to do it this way" but when it shows "this is how this cluster is right now."
kubectl auth can-i --as= is the tool that produces that evidence.
If you ask the apiserver directly instead of reading the RBAC manifests and computing in your head,
you get an exact answer even in complicated cases with overlapping bindings or group inheritance.
The reason to write an EncryptionConfiguration yourself is that the order of the providers array is everything.
If you put identity first, you store in plaintext while believing you turned encryption on.
This mistake raises no error, which makes it hard to notice.
In this lab environment, you cannot change the actual flags of the apiserver process. So steps 2, 3, and 4 take the form of writing inspection artifacts. Also learn that, in the field, responding to the CIS benchmark looks exactly like this — leaving the evidence in documents takes more time than changing the settings.
Steps
- Create the directory
/root/kcsa-hardand the namespacekcsa-hard. - In
/root/kcsa-hard/risky-flags.txt, write the five settings that must not exist in a production apiserver in exactly this form —--anonymous-auth=true,--authorization-mode=AlwaysAllow,--insecure-port=8080,--profiling=true,--service-account-lookup=false. Do not include other lines. - Write an EncryptionConfiguration in
/root/kcsa-hard/encryption-config.yaml— apiVersionapiserver.config.k8s.io/v1, the target resourcesecrets, and providers exactly 2, placingaescbc(key namekey1, with the secret as an actual base64-encoded value) andidentityin the correct order. - In
/root/kcsa-hard/kubelet-checklist.txt, write the four kubelet hardening items in exactly this form —authentication.anonymous.enabled=false,authentication.webhook.enabled=true,authorization.mode=Webhook,readOnlyPort=0. Do not include other lines. - Create the namespaces
kcsa-team-aandkcsa-team-band create the ServiceAccountappin each. Inkcsa-team-a, create the Rolepod-reader(getandlistonpodsin the core group) and the RoleBindingapp-pod-reader(the subject is the SAappinkcsa-team-a). Then runkubectl auth can-i list podswith--as=system:serviceaccount:kcsa-team-a:appagainst each of the two namespaces and confirm that the answers differ. - Ask three questions as an anonymous user and save the results to
/root/kcsa-hard/anon.txtas three lines in키=값format (the placeholders are the key and the value) —get-pods=(get Pods in the namespacekcsa-hard),list-secrets=(list secrets across all namespaces), andhealthz=(get on the non-resource path/healthz). An anonymous subject cannot be queried by impersonating with--as(see below), so create aSubjectAccessReviewwith administrator permission, read.status.allowed, and convert true toyesand false tono. - Write
/root/kcsa-hard/hardening-report.md. The following five lines must appear exactly as section headings —## apiserver,## etcd,## kubelet,## namespace,## anonymous. And the body must mention the following five pieces of evidence —--anonymous-auth=false,aescbc,readOnlyPort=0,kcsa-team-b,system:anonymous.
Notes
- You can ask by impersonating another subject with
--as, as inkubectl auth can-i list pods -n kcsa-team-b --as=system:serviceaccount:kcsa-team-a:app. Impersonation is allowed only with administrator permission. - Only the anonymous subject cannot be queried with
--as.kubectl auth can-icreates a SelfSubjectAccessReview, and that permission is in the ClusterRolesystem:basic-user, whose binding is attached only to thesystem:authenticatedgroup. If you impersonatesystem:anonymous, the apiserver swaps the group tosystem:unauthenticated, so the request itself ends in Forbidden. - So step 6 uses
SubjectAccessReview. This is an object with which an administrator asks on behalf of someone "can this subject do this," so you writeuserandgroupsdirectly inspec. For a resource question, includeresourceAttributes, and for a question about a non-resource path such as/healthz, includenonResourceAttributes, and the answer comes back in.status.allowedas true or false. - You can create the base64 encoding with something like
head -c 32 /dev/urandom | base64. - Common mistake 1: putting
identityfirst in the providers array in step 3. Then you turn encryption on and store in plaintext. - Common mistake 2: memorizing and writing the standard answers in step 6. Grading compares against the result of asking this cluster directly, so you must carry over the values that actually come out of running it.
Prepare the workspace and namespace
Create the directory /root/kcsa-hard and the namespace kcsa-hard.
Create one directory to collect the artifacts and one namespace for the lab. There is an option to create the directory including intermediate paths in one go.
Write the list of dangerous apiserver flags
In /root/kcsa-hard/risky-flags.txt, write the five settings that must not exist in a production apiserver in exactly this form — --anonymous-auth=true, --authorization-mode=AlwaysAllow, --insecure-port=8080, --profiling=true, --service-account-lookup=false. Do not include other lines.
Write them while thinking about which stage each flag disables (authentication, authorization, information exposure, or token validation). The file must contain only the five lines indicated, exactly in the form that includes the values.
Write the EncryptionConfiguration
Write an EncryptionConfiguration in /root/kcsa-hard/encryption-config.yaml — apiVersion apiserver.config.k8s.io/v1, the target resource secrets, and providers exactly 2, placing aescbc (key name key1, with the secret as an actual base64-encoded value) and identity in the correct order.
Order has meaning in the providers array. The order of the two providers follows from the fact that the first is used for writing and all are used for reading. The secret value must be an actual base64-encoded string, and if you leave the placeholder as it is, it will not pass.
Organize the kubelet authentication and authorization check items
In /root/kcsa-hard/kubelet-checklist.txt, write the four kubelet hardening items in exactly this form — authentication.anonymous.enabled=false, authentication.webhook.enabled=true, authorization.mode=Webhook, readOnlyPort=0. Do not include other lines.
Write the field paths of the kubelet configuration file (KubeletConfiguration) in dot notation. They are four items: anonymous access, webhook authentication, the authorization mode, and the read-only port. Think about what becomes meaningless if you use AlwaysAllow for the authorization mode.
Prove namespace isolation with can-i
Create the namespaces kcsa-team-a and kcsa-team-b and create the ServiceAccount app in each. In kcsa-team-a, create the Role pod-reader (get and list on pods in the core group) and the RoleBinding app-pod-reader (the subject is the SA app in kcsa-team-a). Then run kubectl auth can-i list pods with --as=system:serviceaccount:kcsa-team-a:app against each of the two namespaces and confirm that the answers differ.
A Role is valid only in its own namespace. You must show that fact by measurement, not by assertion — if you ask about each of the two namespaces with the same service account, the answers differ. You can ask by impersonating another subject with --as.
Observe what an anonymous user can do
Ask three questions as an anonymous user and save the results to /root/kcsa-hard/anon.txt as three lines in 키=값 format (the placeholders are the key and the value) — get-pods= (get Pods in the namespace kcsa-hard), list-secrets= (list secrets across all namespaces), and healthz= (get on the non-resource path /healthz). An anonymous subject cannot be queried by impersonating with --as (see below), so create a SubjectAccessReview with administrator permission, read .status.allowed, and convert true to yes and false to no.
This is not a step of writing memorized answers. You must carry over the answers that come out from asking this cluster directly. But an anonymous subject cannot be queried with kubectl auth can-i --as=system:anonymous — because an anonymous user has no permission to create the review object that command makes. There is a separate review object with which an administrator asks on their behalf, and you can also ask about non-resource paths with it.
Gather it into a hardening report
Write /root/kcsa-hard/hardening-report.md. The following five lines must appear exactly as section headings — ## apiserver, ## etcd, ## kubelet, ## namespace, ## anonymous. And the body must mention the following five pieces of evidence — --anonymous-auth=false, aescbc, readOnlyPort=0, kcsa-team-b, system:anonymous.
Make a document based on the artifacts of the first five steps. The section headings must be exactly in the indicated form, and the body must contain concrete values backing up each section's conclusion.