Enforce the baseline Pod Security Standard on a namespace (CKS)
Lesson four of the CKS series: Pod Security Standards. One of the pods in our namespace can read the logs of every other pod on its node, including the control plane's. We will shut that down with a single namespace label, preview the impact before we apply it, and capture the exact reason the pod can never come back.
🎥 Watch the video: https://www.youtube.com/watch?v=54tUL5OXedk
This is a CKS Minimize Microservice Vulnerabilities walkthrough. Every command below is real output from a live cluster, and you can reproduce the whole thing yourself (scripts at the end).
The scenario
The setup. In the edge-tools namespace, a Deployment called node-inspector mounts the node's /var/log/pods folder through a hostPath volume. Three things to do. Enforce the baseline Pod Security Standard on the namespace. Delete the running node-inspector pod. Then find the event that explains why it is not recreated, and write it to pss-denial.log.
- ns edge-tools: node-inspector mounts hostPath /var/log/pods
- 1. Enforce the baseline Pod Security Standard on the namespace
- 2. Delete the running node-inspector pod
- 3. Save the event explaining why it is not recreated to pss-denial.log
How Pod Security Admission works
Pod Security Admission is built into every cluster, and you configure it with namespace labels. There are three standards. Privileged allows anything. Baseline blocks the well-known escapes: hostPath volumes, host networking and PID, privileged containers, added capabilities. Restricted goes further and demands things like running as non-root. The label pod-security.kubernetes.io/enforce decides what gets rejected. There are also audit and warn modes that only report. And the key detail: this is an admission check, so it runs when a pod is created. Pods that are already running are left alone.
Why it matters
Two pods running. Now look inside node-inspector at its hostPath mount. Every folder there is another pod's logs, named by namespace. Our own namespace, and then kube-system: coredns, and etcd. Anything those pods print, including tokens and errors that leak data, this one container can read. That is exactly the kind of escape baseline exists to block.
$ kubectl get pods -n edge-tools
NAME READY STATUS RESTARTS AGE
node-inspector-ccd7d54cd-sgbwb 1/1 Running 0 1s
status-page-75c9666db-qjvp8 1/1 Running 0 1s
$ kubectl -n edge-tools exec deploy/node-inspector -- ls /host/pod-logs | head -n 6
edge-tools_node-inspector-ccd7d54cd-sgbwb_6fcb7752-b169-4366-8983-7aea8ac157b0
edge-tools_status-page-75c9666db-qjvp8_f7bf36b0-3324-4112-b0cb-db44325f3638
edge-tools_status-page-75c9666db-x8zs5_ba88a876-ee10-4f31-9267-4ddab254a601
kube-system_coredns-559f6c778d-9jfkc_fdae9f54-94b3-447c-a101-a28fd2841ef3
kube-system_coredns-559f6c778d-fp7rq_dacbc698-80c5-4662-ba8a-3b17a164a1a4
kube-system_etcd-cks-scenario4-control-plane_9a9f430523155faf5e34364a5f598450
No labels yet
Check the namespace labels. Only the automatic metadata name label. No Pod Security labels, which means the cluster default applies, and on most clusters that default is privileged: anything goes.
$ kubectl get ns edge-tools --show-labels
NAME STATUS AGE LABELS
edge-tools Active 1s kubernetes.io/metadata.name=edge-tools
Preview with a dry run
Before enforcing anything in a real cluster, preview it. The same label command with --dry-run=server asks the API server to evaluate the change without saving it. It warns that an existing pod would violate baseline, names the pod, and names the reason: hostPath volumes. Exactly the one we expected, and nothing else.
$ kubectl label --dry-run=server --overwrite ns edge-tools pod-security.kubernetes.io/enforce=baseline
Warning: existing pods in namespace "edge-tools" violate the new PodSecurity enforce level "baseline:latest"
Warning: node-inspector-ccd7d54cd-sgbwb: hostPath volumes
namespace/edge-tools labeled (server dry run)
Enforce baseline
Now apply it for real. Same warning, and the namespace is labelled. But look at the pods: node-inspector is still running. Enforcement happens at admission, when a pod is created. Kubernetes will not kill a running workload because a label changed, which is why the task tells you to delete it yourself.
$ kubectl label --overwrite ns edge-tools pod-security.kubernetes.io/enforce=baseline
Warning: existing pods in namespace "edge-tools" violate the new PodSecurity enforce level "baseline:latest"
Warning: node-inspector-ccd7d54cd-sgbwb: hostPath volumes
namespace/edge-tools labeled
$ kubectl get pods -n edge-tools
NAME READY STATUS RESTARTS AGE
node-inspector-ccd7d54cd-sgbwb 1/1 Running 0 1s
status-page-75c9666db-qjvp8 1/1 Running 0 1s
Delete, and it stays gone
Delete the node-inspector pod. Normally its ReplicaSet would have a replacement running within seconds. Not this time: only status-page is left, and it keeps running because it complies. The Deployment shows zero of one ready. The ReplicaSet is trying, and admission is refusing.
$ kubectl -n edge-tools delete pod -l app=node-inspector
pod "node-inspector-ccd7d54cd-sgbwb" deleted from edge-tools namespace
$ kubectl get pods -n edge-tools
NAME READY STATUS RESTARTS AGE
status-page-75c9666db-qjvp8 1/1 Running 0 7s
$ kubectl get deploy -n edge-tools
NAME READY UP-TO-DATE AVAILABLE AGE
node-inspector 0/1 0 0 7s
status-page 1/1 1 1 7s
Save the reason
The refusal is recorded as an event with reason FailedCreate. A field selector pulls exactly that event, custom columns keep just the reason and the message, and we redirect it into pss-denial.log. The message says it all: forbidden, violates PodSecurity baseline, hostPath volumes, and the volume name, pod-logs. That line is what the task wants in the file. kubectl describe on the ReplicaSet shows the same event if you prefer to copy it from there.
$ kubectl -n edge-tools get events --field-selector reason=FailedCreate -o custom-columns=REASON:.reason,MESSAGE:.message --no-headers | head -n 1 > pss-denial.log && cat pss-denial.log
FailedCreate Error creating: pods "node-inspector-ccd7d54cd-ktnj6" is forbidden: violates PodSecurity "baseline:latest": hostPath volumes (volume "pod-logs")
Exam tips
Tips for the exam. The labels are in the Pod Security Standards page of the docs; search for pod security admission and copy the label key rather than typing it. Use the imperative kubectl label with dash dash overwrite, it is faster than editing the namespace. Enforce is the mode that blocks; warn and audit only report. Remember running pods are not evicted, so delete them when asked. And the reason lives on the ReplicaSet, not the Deployment: describe the ReplicaSet or filter events by FailedCreate.
- Copy the label key from the Pod Security Admission docs
- kubectl label --overwrite ns pod-security.kubernetes.io/enforce=baseline
- enforce blocks; warn and audit only report
- Running pods are not evicted: delete them
- The reason is on the ReplicaSet: FailedCreate event
Recap
- hostPath /var/log/pods = every pod's logs
- --dry-run=server first, then enforce=baseline
- Delete the pod: the ReplicaSet is refused
- FailedCreate event saved; subscribe + dev.to writeup
Reproduce this yourself
The entire scenario is scripted on a throwaway kind cluster: https://github.com/The-Cyber-Sidekick/TCS_CKS_2026_Exam_Scenarios
git clone https://github.com/The-Cyber-Sidekick/TCS_CKS_2026_Exam_Scenarios.git
cd TCS_CKS_2026_Exam_Scenarios/scenario4-pod-security-baseline
./setup.sh # creates the cluster AND arms the scenario
# solve it by hand, or:
./solution.sh # apply the answer key and verify
If this helped, subscribe to The Cyber SideKick on YouTube for more CKS drills, and grab the newsletter at https://thecybersidekick.beehiiv.com.
Top comments (0)