TT Lab
Get started
Learn Learning paths Courses

KCSA — Kubernetes Security Associate

Applying Pod Security Admission With Labels

Continue in TT Lab

Goal

Apply a Pod security policy with namespace labels alone, confirm that a restricted-violating Pod is actually rejected and see the rejection message, and finally observe directly PSA's key property that raising the level does not evict Pods that are already running.

Why it matters

PSP failed not because it lacked features but because it was impossible to debug. When several PSPs matched, it was hard to tell which one was applied, and since it was mutating, the spec I wrote and the spec that ran were different. A security feature that is not used is not security.

PSA was built from that lesson. One label is the policy, and the rejection message lists all the violating fields. It is an exchange that lost expressiveness and gained operability, and understanding why such an exchange was right is a point often asked in KCSA.

Step 7 is the highlight of this lab. PSA acts only at admission time, so even if you raise enforce to restricted, Pods that are already running stay alive. It looks as if nothing happened, and then it blows up at the next rollout or node replacement when Pods fail to come up. If you confirm by hand that there is a time lag between the policy change and the incident, it changes how you design a migration.

Steps

  1. Create the namespace kcsa-psa-baseline and attach four labels — pod-security.kubernetes.io/enforce=baseline, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.
  2. Create the namespace kcsa-psa-restricted and attach four labels — pod-security.kubernetes.io/enforce=restricted, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.
  3. Try to create a Pod bad with a privileged container in kcsa-psa-restricted. It must be rejected, and you save the entire rejection message to /root/kcsa-psa/reject.txt. The Pod bad must remain not created to the end.
  4. Create a Pod app-baseline in kcsa-psa-baseline — image nginx:1.27-alpine, and specify nothing for the securityContext. Take it through to Running.
  5. Create a Pod app-restricted in kcsa-psa-restricted — image nginx:1.27-alpine, at the Pod level securityContext.runAsNonRoot: true and securityContext.seccompProfile.type: RuntimeDefault, and at the container level allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], and runAsUser: 1000.
  6. Write an AdmissionConfiguration in /root/kcsa-psa/exempt.yaml — plugins[0].name is PodSecurity, configuration.kind is PodSecurityConfiguration, defaults.enforce is baseline, and the first item of exemptions.namespaces is kube-system. And in /root/kcsa-psa/exempt-note.txt, write the three axes of PSA exemption, one per line, exactly as usernames, runtimeClasses, namespaces.
  7. Change only the enforce label of kcsa-psa-baseline to restricted. Then (a) check the current state of the existing Pod app-baseline and write it to /root/kcsa-psa/upgrade.txt as existing-pod=<상태> (the placeholder is the Pod's status), and (b) try to create a Pod app-baseline2 with the same spec as step 4, confirm that it is rejected, and add the line new-pod=rejected to the same file. The Pod app-baseline2 must remain not created.

Notes

Create the baseline namespace

Create the namespace kcsa-psa-baseline and attach four labels — pod-security.kubernetes.io/enforce=baseline, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.

PSA works only through namespace labels. There are three modes (enforce, audit, and warn), and one more label for version pinning is added to them. Decide the values while thinking that enforce is the level that actually blocks, and audit and warn are the level that previews "what would break if we raised it."

Create the restricted namespace

Create the namespace kcsa-psa-restricted and attach four labels — pod-security.kubernetes.io/enforce=restricted, pod-security.kubernetes.io/enforce-version=v1.30, pod-security.kubernetes.io/audit=restricted, pod-security.kubernetes.io/warn=restricted.

They are the same four labels as in the previous step, but the values are all the strictest level. If you leave out the version label, the policy content may change when you upgrade the cluster.

Confirm that a restricted-violating Pod is rejected

Try to create a Pod bad with a privileged container in kcsa-psa-restricted. It must be rejected, and you save the entire rejection message to /root/kcsa-psa/reject.txt. The Pod bad must remain not created to the end.

The goal of this step is to produce a failure. If the command fails, the message comes out on standard error, so watch your redirection. The rejection message states which profile and which fields are the problem.

An ordinary Pod that passes only baseline

Create a Pod app-baseline in kcsa-psa-baseline — image nginx:1.27-alpine, and specify nothing for the securityContext. Take it through to Running.

It is an ordinary Pod with no special security settings at all. It should pass baseline but satisfy none of the restricted requirements. It is the control group for comparison in the next steps, so do not harden it.

A Pod that passes restricted

Create a Pod app-restricted in kcsa-psa-restricted — image nginx:1.27-alpine, at the Pod level securityContext.runAsNonRoot: true and securityContext.seccompProfile.type: RuntimeDefault, and at the container level allowPrivilegeEscalation: false, capabilities.drop: ["ALL"], and runAsUser: 1000.

Four requirements are needed, and two of them go in the Pod-level securityContext and two at the container level. If you confuse which goes where, the rejection message tells you. You must also specify runAsUser explicitly.

Organize the exemption configuration and exemption axes

Write an AdmissionConfiguration in /root/kcsa-psa/exempt.yaml — plugins[0].name is PodSecurity, configuration.kind is PodSecurityConfiguration, defaults.enforce is baseline, and the first item of exemptions.namespaces is kube-system. And in /root/kcsa-psa/exempt-note.txt, write the three axes of PSA exemption, one per line, exactly as usernames, runtimeClasses, namespaces.

Exemptions cannot be expressed with namespace labels, so they go in the apiserver's admission configuration file. In this lab you only write that file. There are three exemption axes, and think about what each of the three uses as the basis for skipping the check.

What happens to existing Pods when only the label is raised

Change only the enforce label of kcsa-psa-baseline to restricted. Then (a) check the current state of the existing Pod app-baseline and write it to /root/kcsa-psa/upgrade.txt as existing-pod=<상태> (the placeholder is the Pod's status), and (b) try to create a Pod app-baseline2 with the same spec as step 4, confirm that it is rejected, and add the line new-pod=rejected to the same file. The Pod app-baseline2 must remain not created.

PSA judges only at admission time. That fact decides the result when you raise the level. If you check the existing Pod's state directly and write it down as it is, and then try to create a new Pod with the same spec, the contrast becomes clear.