open atlas
↑ Back to track
Cloud & Infra Security CLOUD · 02 · 04

Network Policy and Pod Security

A fresh Kubernetes pod is wide open: any pod can reach any other on the flat network, and without a SecurityContext it can run as root with host access. NetworkPolicy and Pod Security Standards are the two opt-in gates that turn that default-allow posture into default-deny.

CLOUD Middle ◷ 16 min
Level
FoundationsJuniorMiddleSenior

A penetration tester pops a single low-value pod — an image-resizer behind a public upload endpoint, vulnerable to an SSRF. On a hardened host that would be the end of it: one compromised process in one container. But this is a default Kubernetes cluster, so two doors are already open. First, the pod can open a TCP connection to every other pod in the cluster: the database, the internal admin API, the metrics endpoint that leaks tokens, the cloud metadata service at 169.254.169.254. There is no firewall between workloads because nobody wrote one. Second, the pod’s container is running as UID 0 with the default SecurityContext, so the SSRF-to-RCE chain hands the attacker root inside a container that can mount, load kernel modules through a permissive capability set, and probe the node. Within twenty minutes the resizer is a pivot point into the whole cluster. Kubernetes did exactly what it promised: it scheduled the pod and let it talk to everything. Nobody told it not to.

By the end of this lesson you’ll be able to write a default-deny NetworkPolicy and read an existing one for the gap that quietly re-opens it, apply a Pod Security Standards profile that blocks the root/privileged/hostPath escapes, and explain why both gates are opt-in and why that default bites teams every time.

Two default-allow doors: the network and the pod spec

Kubernetes security has a recurring shape: the platform ships default-allow, and security is something you add. Two of those defaults sit in this lesson, and they are independent — closing one does nothing for the other.

Door one — the flat network. The Kubernetes networking model requires that every pod can reach every other pod by IP, across all namespaces, with no NAT. That’s not a bug; it’s the contract the CNI plugin must satisfy. The consequence is that east-west traffic (pod-to-pod) is wide open by default. A NetworkPolicy is the opt-in object that restricts it — but only if your CNI implements NetworkPolicy enforcement (Calico, Cilium, and most managed CNIs do; the legacy flannel default does not, so a policy you kubectl apply succeeds and silently enforces nothing).

Door two — the pod’s SecurityContext. A container with no securityContext runs as whatever UID the image specifies — frequently root (UID 0) — and inherits a default Linux capability set, can be granted privileged: true, can mount hostPath volumes, and can share the host’s PID or network namespace. Each of those is a documented container-escape primitive. Pod Security Standards (PSS) define three profiles — privileged (no restrictions), baseline (block the well-known escapes), and restricted (heavily locked down: non-root, no added capabilities, seccomp on) — and Pod Security Admission (PSA) is the built-in admission controller that enforces a chosen profile per namespace via labels.

NetworkPolicy: additive allow, and the default-deny pattern

A NetworkPolicy is allow-only and additive, exactly like RBAC — there is no deny rule. The trick that makes it behave like a firewall is the selector: the moment any NetworkPolicy selects a pod for a given direction (ingress or egress), that pod flips from default-allow to default-deny for that direction, and only the traffic explicitly listed in any policy selecting it is permitted. The union of all matching policies is the allow-list.

So the canonical hardening move is a per-namespace default-deny policy that selects every pod but lists no allowed peers:

PolicySelector + directionEffectFoot-gun if omitted
default-deny-allpodSelector: {}, both Ingress + EgressEvery pod in the namespace drops all traffic until allowedFlat network — popped pod reaches the whole cluster
allow-dnsegress to kube-dns on UDP/TCP 53Lets pods resolve names once egress is deniedEvery pod breaks — DNS is the first thing default-deny kills
allow-from-frontendingress to app: api from app: webOnly the web tier may reach the API tierAny pod can hit the API directly, bypassing the edge
block-metadataegress deny to 169.254.169.254/32Stops SSRF-to-cloud-credential theft from any podSSRF in one pod reads the node’s IAM role / instance creds

The non-obvious failure mode lives in the second row. The instant you apply a default-deny egress policy, you also block the pod’s DNS lookups to kube-dns, because that is egress to another pod on port 53. Every request then fails not with “connection refused” but with a name resolution error, which sends engineers debugging the wrong layer for an hour. A correct default-deny is therefore always a pair: deny-all plus an explicit allow-DNS egress rule. This is the single most common reason teams roll back NetworkPolicy and declare it “too hard.”

Common mistake

A NetworkPolicy that kubectl apply accepts is not the same as a NetworkPolicy that is enforced. NetworkPolicy is an API object stored in etcd; enforcement is the CNI plugin’s job, and not every CNI does it. On a cluster running plain flannel (or with the wrong Cilium/Calico mode), your carefully written default-deny is persisted, shows up in kubectl get netpol, and drops exactly zero packets. Always verify enforcement empirically — kubectl exec into a pod the policy should isolate and try to reach a peer it should not — rather than trusting that the object exists. “The policy is applied” and “the traffic is blocked” are two different claims.

Pod Security Standards: blocking the escape, not just the network

NetworkPolicy contains lateral movement between pods. It does nothing about a pod that escapes downward to the node — and that’s what an unconstrained SecurityContext allows. The three escapes that matter, and what the restricted profile does to each:

  1. Running as root. runAsNonRoot: true plus a non-zero runAsUser means a container escape lands the attacker as an unprivileged UID, not root, sharply limiting what they can do on the node if they break out of the namespace.
  2. privileged: true and added capabilities. A privileged container has essentially all capabilities and device access — it is a near-direct path to the host. restricted forbids privileged, requires dropping ALL capabilities, and allows back only NET_BIND_SERVICE.
  3. Host namespaces and hostPath. hostPID: true / hostNetwork: true share the node’s process or network view; a hostPath volume can mount / and read the kubelet’s credentials. restricted and baseline both block these.

Pod Security Admission enforces a profile by namespace label, in up to three modes — enforce (reject non-compliant pods), audit (log them), and warn (warn the applier) — so you can roll out in warn/audit first and watch what would break before flipping to enforce:

Pick the best fit

You're hardening a namespace of stateless app pods that never need root, host access, or extra capabilities. Pick the Pod Security posture.

Quiz

You apply a default-deny `Ingress` + `Egress` NetworkPolicy to a namespace and immediately every pod fails with name-resolution errors. What happened?

Quiz

A namespace enforces the `restricted` Pod Security profile, but the cluster's CNI does not implement NetworkPolicy. A pod is compromised via RCE. What is the attacker's realistic next move?

Order the steps

Order these steps to harden a namespace safely without an outage, from first to last:

  1. 1 Label the namespace `warn`/`audit` for the `restricted` profile and watch what would be rejected
  2. 2 Fix the SecurityContext of pods the audit flags (runAsNonRoot, drop capabilities)
  3. 3 Flip Pod Security to `enforce: restricted` once audit is clean
  4. 4 Apply a default-deny ingress+egress NetworkPolicy plus an explicit allow-DNS rule
  5. 5 Add narrow allow rules for the real traffic, then verify enforcement with kubectl exec
Recall before you leave
  1. 01
    Walk through exactly why a NetworkPolicy `kubectl apply` can succeed and still block zero traffic, and how you'd prove enforcement.
  2. 02
    Why are NetworkPolicy and Pod Security Standards complementary rather than redundant, and what does each fail to cover?
Recap

A fresh Kubernetes pod has two independent default-allow doors. The network is flat: every pod can reach every other pod by IP across namespaces, so east-west traffic is wide open until a NetworkPolicy restricts it. NetworkPolicy is additive and allow-only (no deny rule) — selecting a pod flips it to default-deny for that direction, and the canonical hardening is a default-deny pair: deny-all ingress+egress plus an explicit allow-DNS egress rule, because the first thing a naive egress-deny breaks is name resolution to kube-dns. Critically, NetworkPolicy only enforces if the CNI implements it, so “applied” must be verified against “enforced” by testing the dataplane with kubectl exec. The second door is the pod’s SecurityContext: with no constraint a container runs as root with a default capability set and can request privileged, hostPath, and host namespaces — each a node-escape primitive. Pod Security Standards define privileged/baseline/restricted profiles, and Pod Security Admission enforces one per namespace via a label, in enforce/audit/warn modes so you stage the rollout before it starts rejecting pods. The two controls are orthogonal: NetworkPolicy contains lateral movement, PSS blocks node escape, and a compromise only needs one open door — so you apply both, plus minimal ServiceAccount RBAC. When you next review a namespace, the three questions are: is there a default-deny NetworkPolicy (and does the CNI enforce it), what Pod Security profile is enforced, and can any pod still reach the metadata endpoint?

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 6 done
Connected lessons

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

Apply this

Put this lesson to work on a real build.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.