Dev.to Security 🔐 Cybersecurity 👁 0 📖 5 min read

Enforce the baseline Pod Security Standard on a namespace (CKS)

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 co

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.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.