TT Lab
Get started
Learn Learning paths Courses

KCSA — Kubernetes Security Associate

Auditing Control Plane Hardening

Continue in TT Lab

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

  1. Create the directory /root/kcsa-hard and the namespace kcsa-hard.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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

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.