Module 11 ยท Lesson 11.2
Pod Security Standards & NetworkPolicies
RBAC controls who can change the cluster's desired state. These two features control something different: what a running container is actually allowed to do, and who it's allowed to talk to.
Pod Security Standards: constraining the container itself
Kubernetes defines three standard security profiles: Privileged (no restrictions โ effectively opt-out), Baseline (blocks known privilege-escalation paths, a sane minimum), and Restricted (hardened: no root, no privilege escalation, a locked-down syscall filter). You apply one by labeling a namespace:
pod-security.kubernetes.io/enforce: restricted
No separate controller to install โ this is built into API admission.
Once set, any pod spec that violates the profile is rejected at
kubectl apply time, not silently allowed and flagged later. You'll see
this directly in the lab: applying a pod without the right
securityContext into a restricted namespace never creates anything โ
the rejection message lists exactly which constraints it violated
(running as root, missing seccomp profile, privilege escalation allowed).
Linux aside: most of what "restricted" enforces maps to real Linux primitives โ
runAsNonRootis about the process's Linux UID (don't run as UID 0),seccompProfilerestricts which syscalls the process can make at all, and droppingcapabilitiesremoves specific root-like powers (like binding privileged ports) without granting full root. Containers don't have their own permission model โ they inherit Linux's.
NetworkPolicies: constraining who a pod can talk to
By default, every pod in a cluster can reach every other pod โ namespaces
don't restrict this (Module 2). A NetworkPolicy changes that, for the
pods it selects, using the same label-selector pattern you've used since
Module 2: podSelector picks which pods the policy applies to, then
ingress/egress rules say what's allowed in or out โ selected by pod
labels, not IP addresses, so the rule keeps working as pods are
replaced.
Default-deny, then allow by label
An empty podSelector: {} with policyTypes: [Ingress] and no ingress
rules is a classic default-deny: it matches every pod in the
namespace and allows nothing in. From there you add narrow ingress rules
naming exactly which pods (by label) may reach which other pods โ the
same default-deny-then-allow shape as a security group, just selector-based
instead of IP/CIDR-based.
One real caveat worth knowing: NetworkPolicy objects always exist as API
objects, but enforcement is the CNI plugin's job, not the API
server's. This cluster runs Calico specifically because it enforces
NetworkPolicy; some CNIs (including kind's own default) silently accept
the objects and do nothing. Always confirm your CNI actually enforces
before trusting a policy in production.
The infrastructure analogy
A NetworkPolicy is a security group that follows the workload instead of
an IP โ you write "allow from anything labeled role: trusted" instead of
"allow from 10.0.4.0/24." Pod Security Standards are closest to a
hardened AMI/container baseline enforced at admission instead of
discovered at runtime.
In the lab you'll trip a Restricted-namespace rejection and fix it, then prove a NetworkPolicy actually blocks (and selectively allows) real traffic โ not just that the YAML applied.
๐งช Lab: lab-23-pod-security-and-networkpolicies
Preview onlyGoal
Trip a Pod Security admission rejection and fix it, then write a NetworkPolicy that actually blocks (and selectively allows) real traffic โ not just YAML that applies cleanly.
Tasks
Part 1 โ Pod Security Standards
- Apply the starting manifests:
kubectl apply -f manifests/ - Notice
pod/appis rejected outright โ read the error message, it lists every constraint it violates. - Edit
manifests/app-pod.yaml: add a pod-level and container-levelsecurityContextthat satisfiesrestricted(non-root user, a seccomp profile, no privilege escalation, all capabilities dropped). - Re-apply:
kubectl apply -f manifests/app-pod.yamlโ it should now be created and reachRunning.
Part 2 โ NetworkPolicy
server,client-a, andclient-bshould already beRunning(they were compliant from the start). Confirmclient-acan currently reachserverโ get the server's pod IP (kubectl get pod server -n lab-23-pod-security-and-networkpolicies -o jsonpath='{.status.podIP}') and fromclient-a:kubectl exec -n lab-23-pod-security-and-networkpolicies client-a -- wget -q -T 3 -O- http://<server-ip>:8080- Write a NetworkPolicy selecting
app: serverthat denies all ingress except from pods labeledrole: trusted. Add it as a new file undermanifests/and apply it. - Re-run the step 5 command from
client-aโ it should now fail/timeout. client-bis currently labeledrole: untrustedtoo (that's a bug โ it's supposed to be the allowed one). Fix its label:kubectl label pod client-b -n lab-23-pod-security-and-networkpolicies role=trusted --overwrite- Run the same
wgetcommand fromclient-binstead โ it should succeed.
Check
Run the check once both parts are done.
This lab runs against a real local Kubernetes cluster with an automated grader โ clone the repo and run make start to do it for real.
๐ Quiz
1. You label a namespace `pod-security.kubernetes.io/enforce: restricted`. A pod spec that violates it is applied. What happens?
2. Which of these does the 'restricted' Pod Security Standard typically require? (select all that apply)
3. What does `podSelector: {}` with `policyTypes: [Ingress]` and no ingress rules do?
4. You apply a default-deny NetworkPolicy on `kind`'s default kindnet CNI (not Calico) and test that traffic is blocked. What should you expect?scenario
5. A NetworkPolicy allows traffic 'from pods labeled role: trusted' rather than from a specific IP range. Why is that generally better inside a cluster?
Progress isn't saved in this preview โ run the course locally to track completion and grade labs for real.