The first time lateral movement really clicked for me wasn’t from a whitepaper. It was from a broken app. Kubernetes Networkpolicies : A Simple Approach To Reduce Lateral Movement
We had a compromised pod. Nothing fancy an exposed debug endpoint that shouldn’t have been there. The attacker didn’t need cluster-admin. They didn’t need to break Kubernetes itself. They just started poking around the network.
And it worked.
They could reach internal services that were never meant to be public. Databases. Admin APIs. Metrics endpoints with way too much information. All because, by default, Kubernetes lets every pod talk to every other pod.
This is where Kubernetes is both powerful and dangerous.
-
Pods are cheap and dynamic
-
IPs change constantly
-
Services abstract everything behind stable DNS names
-
Flat networking is the default
That combination is fantastic for developers and terrible for blast radius.
Once you’re inside the cluster, lateral movement is easy unless you do something about it.
NetworkPolicies are one of the fastest, most pragmatic ways to reduce that blast radius. Not eliminate it. Reduce it.
- They won’t stop every attack.
- They won’t replace auth.
- They won’t save you from bad app design.
But they will stop a compromised pod from freely wandering around your cluster like it owns the place.
If and this is a big if you actually understand how they work.
What a NetworkPolicy actually controls
This section is where most people get burned. I certainly did.
Pods vs Service
NetworkPolicies apply to pods, not Services.
Always. No exceptions.
A Service is just a virtual IP and some iptables (or IPVS) rules. When traffic hits a Service, it is forwarded to a backend pod. The policy is evaluated at the pod, not the Service.
This leads to a very common misunderstanding:
- “But the Service is open, why is traffic blocked?”
- Because the pod is isolated.
If you remember nothing else from this article, remember this:
Ingress vs Egress isolation
NetworkPolicies are directional:
-
Ingress
traffic to a pod
-
Egress
traffic from a pod
You can apply one, the other, or both.
Important detail
ingress and egress are independent.
I’ve seen teams lock down ingress and then wonder why data exfiltration is still possible. Or lock down egress and accidentally break DNS for half the cluster (we’ll get to that).
The “deny by default only after selection” rule
This is subtle and critical.
A pod is not isolated unless at least one NetworkPolicy selects it:
-
No policies select the pod → all traffic allowed
-
At least one policy selects the pod → traffic not explicitly allowed is denied
This means:
-
Adding your first policy is the dangerous moment
-
Order of rollout matters a lot
-
You can have policies in a namespace that do nothing if they select nothing
I’ve seen entire clusters “secured” by policies that didn’t match a single pod.
How selectors actually work in real clusters
-
No regex
-
No wildcards
-
No “starts with”
And yes, labels are case-sensitive. Ask me how I know.
- The number one real-world failure mode:
- You think a selector matches. It doesn’t.
NetworkPolicies are allow-lists, not firewalls
This mindset shift is important.
NetworkPolicies do not say “block X”.
They say “allow Y”.
Everything else is implicitly denied once the pod is isolated.
If you approach them like traditional firewalls, you’ll fight Kubernetes the whole way.
Your CNI matters more than people think
NetworkPolicies are just an API.
Enforcement is done by your CNI plugin:
-
Calico
-
Cilium
-
Antrea
-
etc.
If your CNI doesn’t support NetworkPolicies, nothing happens. Kubernetes won’t warn you. Policies will happily exist and do absolutely nothing.
Even among CNIs that do support them:
-
Feature sets differ
-
Logging differs
-
Performance characteristics differ
This matters a lot when debugging.
A safe default baseline that won’t wreck your cluster
This is the part people usually rush and regret.
Step 1: Default deny ingress first
If you do one thing, do this.
Default deny ingress reduces blast radius without breaking most workloads immediately.
What this does:
-
Selects all pods in the namespace
-
Denies all incoming traffic unless explicitly allowed
Why ingress first?
-
Most apps don’t expect random inbound traffic
-
Outbound dependencies still work
-
You can add allow rules incrementally
This is the safest place to start.
Step 2: Allow what must reach your pods
Common examples:
-
Ingress controllers
-
Same-app communication
-
Monitoring / scraping
Example:
Step 3: DNS the silent killer
If you introduce egress policies without allowing DNS, things will break. Hard.
Symptoms:
-
Pods can’t resolve hostnames
-
Everything times out
-
People blame Kubernetes, not DNS
DNS usually means:
-
UDP/TCP 53
-
kube-dns / CoreDNS namespace
This is the most common “we broke prod” moment I’ve seen.
Step 4: Default deny egress
Egress policies are powerful and dangerous.
Do not start here.
When you do introduce them:
-
Inventory dependencies first
-
Expect surprises
-
Roll out namespace by namespace
Egress control is where NetworkPolicies start paying real security dividends but only if you’re ready.
What not to lock down first
-
kube-system
-
Shared infra namespaces you don’t fully understand
-
Anything without owners
Locking down mystery workloads is how you get paged.
How to debug blocked traffic
This is the 2am playbook.
Step 1: Are policies actually enforced?
First question, always:
Does my CNI enforce NetworkPolicies?
Check:
-
CNI docs
-
A simple deny-all test
-
Known-working examples
If enforcement isn’t happening, stop. Everything else is noise.
Step 2: Is the pod isolated?
Then:
-
Check which policies select it
-
Confirm matches
No matching policy → no isolation
Step 3: Label and selector mismatches
Check:
Compare actual labels to selectors.
Most bugs live here.
Step 5: Ports and protocols
NetworkPolicies are explicit.
-
TCP ≠ UDP
-
Port 80 ≠ 8080
-
Named ports only work if defined correctly in the pod spec
“I allowed the port” is meaningless unless it’s the right port and protocol.
Step 6: “But the Service port is open”
Services don’t matter here.
The backend pod port is what’s enforced.
This misunderstanding causes endless confusion.
Step 7: CNI-specific tooling
At this point, use your CNI:
-
Calico flow logs
-
Cilium Hubble
-
Antrea traceflow
These tools tell you what was dropped and why.
Use them.
A simple NetworkPolicy “starter pack”
Most namespaces need surprisingly little:
-
Default deny ingress
-
Allow from ingress controller (if applicable)
-
Allow same-app traffic
-
Allow DNS (when egress is enabled)
Apply incrementally:
-
One namespace
-
One policy
-
Observe
-
Expand
To back out:
-
Delete the policy
-
Traffic immediately returns
This reversibility is one of the nicest things about NetworkPolicies.
How to roll this out in real teams
Dev → staging → prod
Always.
NetworkPolicies surface undocumented dependencies. That’s painful and useful.
Observability before enforcement
If you don’t know who talks to whom:
-
You’re guessing
-
Guessing breaks things
Flow logs are worth the effort.
Document dependencies
NetworkPolicies force conversations teams avoid:
-
“Why does this app talk to that?”
-
“Should it?”
This creates friction. That’s not a bug.
Cultural mistakes
-
Security teams dropping policies without context
-
Platform teams saying “just fix your app”
-
No rollback plan
NetworkPolicies are as much about people as YAML.
You Might Be Interested In
- Secure Code Review Checklist For Prs: What To Look For In 15 Minutes
- What Data Security Really Means?
- How Phishing Attacks Trick Users?
- Why DevOps Improves Software Delivery?
- Content Security Policy CSP: a starter policy you can deploy safely
Conclusion
Kubernetes NetworkPolicies are not a silver bullet, and treating them like one is how teams get frustrated or break production. They won’t fix insecure applications, they won’t replace authentication, and they won’t give you perfect security. What they will do is drastically reduce blast radius when something goes wrong which, in real systems, is what matters most.
The key is having the right mental model. NetworkPolicies apply to pods, not Services. They are allow-lists, not firewalls. Nothing is blocked until a pod is selected, and once it is, everything not explicitly allowed is denied. If you internalize that, most of the confusing behavior suddenly makes sense.
Start small. Default deny ingress in one namespace. Observe. Debug. Learn where your assumptions about traffic were wrong. Only then move on to egress controls and tighter isolation. Every production cluster I’ve seen that successfully uses NetworkPolicies got there iteratively, with a few scars along the way.
You don’t need perfect policies. You need better ones than “everything can talk to everything.” Reduce lateral movement, shrink the blast radius, and iterate. That’s how NetworkPolicies actually earn their keep in real Kubernetes clusters.
FAQs
Do NetworkPolicies block traffic by default?
No and this is one of the most common and dangerous misunderstandings. Kubernetes does not block anything by default. A pod only becomes isolated after at least one Network Policy selects it. Until that moment, it can send traffic anywhere and receive traffic from anywhere inside the cluster.
This is why people sometimes deploy NetworkPolicies and think they’re protected, when in reality nothing has changed. The first policy that matches a pod flips it from “fully open” to “deny by default except what’s allowed.” That transition point is where most outages happen, because suddenly all the implicit connectivity your apps relied on disappears unless you explicitly allow it.
What’s the safest NetworkPolicy to start with?
In my experience, the safest place to start is a default deny ingress policy scoped to a single, well-understood namespace. This reduces lateral movement immediately without breaking outbound dependencies like databases, external APIs, or DNS.
Ingress-only isolation forces you to think about who should be allowed to reach this workload, which is usually a smaller and more obvious set. It also lets you build confidence with selectors, labels, and debugging before you move into egress control which is where things get much easier to break.
Why did my app break after enabling default deny egress?
Nine times out of ten, it’s DNS. When you enable default deny egress, you’re telling Kubernetes: “this pod may not talk to anything unless I explicitly allow it.” That includes CoreDNS. Once DNS resolution breaks, everything that relies on hostnames starts failing in ways that look unrelated database connections, HTTP calls, even some SDKs.
The other common cause is undocumented dependencies. Apps often talk to things no one remembered to mention: metadata services, external auth providers, message brokers in another namespace. Egress policies have a way of exposing these hidden couplings very quickly, which is painful but ultimately useful.
Do NetworkPolicies secure Services?
No. NetworkPolicies do not secure Services themselves they secure the pods behind them. A Service is just a routing abstraction. Traffic still ends up at a pod, and that’s where the policy decision is enforced.
This distinction matters a lot when debugging. You can have a perfectly reachable Service IP and still see traffic dropped because the backend pod is isolated and doesn’t allow that source. If you think in terms of “opening” or “closing” Services, NetworkPolicies will never quite make sense.
How do I tell which policy is blocking traffic?
Start by identifying which NetworkPolicies select the affected pod. If a pod is isolated, then some policy applies even if it’s not the one you think. From there, compare the actual source labels, namespace labels, ports, and protocols against what the policy allows.
If that still isn’t obvious, this is where CNI-specific tooling becomes invaluable. Flow logs and tracing tools can show you exactly which rule caused a drop. Without that signal, people tend to guess and guessing with NetworkPolicies usually makes things worse, not better.

