← Back to blog

2026-09-10 · 7 min read

Kubernetes Network Policies: Locking Down Pod Traffic

By default every pod can talk to every other pod. Network Policies fix that. A practical guide to default-deny, ingress/egress rules, and zero-trust networking in Kubernetes.

#kubernetes#security#networking#zero-trust#devsecops
Kubernetes Network Policies: Locking Down Pod Traffic

Kubernetes Network Policies: Locking Down Pod Traffic

Here's something that surprises people new to Kubernetes: by default, every pod can reach every other pod in the cluster. No firewall, no segmentation. A compromised frontend pod can talk straight to your database, your internal admin service, and everything else. Network Policies are how you close that down, and they're criminally underused.

The default is wide open

Kubernetes networking is flat. Any pod can initiate a connection to any other pod by IP, across namespaces, with nothing in the way. For a single trusted app this is convenient. For a real multi-service cluster, it means one popped container has lateral movement to everything.

Network Policies let you declare which pods can talk to which: a per-pod firewall expressed as Kubernetes resources, enforced by your CNI.

You need a CNI that enforces them

A critical gotcha: NetworkPolicy objects are inert unless your CNI plugin enforces them. Calico, Cilium, and Weave do. The default kubenet and some basic setups do not: your policies will apply with no effect and a false sense of security. Check your CNI first.

Start with default-deny

The right foundation is default-deny, then allow what's needed: zero trust at the network layer. This policy denies all ingress to every pod in a namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: app
spec:
  podSelector: {}        # all pods in the namespace
  policyTypes:
    - Ingress

An empty podSelector selects every pod; listing Ingress with no ingress: rules means "allow nothing in." Now nothing can reach these pods until you explicitly permit it. Do the same for egress once you're confident.

Then allow specific paths

Let the frontend reach the API, and only the API reach the database:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow-from-frontend
  namespace: app
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: db-allow-from-api
  namespace: app
spec:
  podSelector:
    matchLabels: { app: postgres }
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector:
            matchLabels: { app: api }
      ports:
        - { protocol: TCP, port: 5432 }

Now the frontend physically cannot reach the database, even if it's compromised. That's the win: blast-radius containment.

Don't forget egress and DNS

Egress policies control what pods can reach outbound. They're powerful for stopping data exfiltration, but there's a classic trap: lock down egress and you'll break DNS unless you explicitly allow it. Always permit DNS to kube-dns:

  egress:
    - to:
        - namespaceSelector: {}
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }

Cross-namespace rules

Use namespaceSelector to allow traffic from another namespace (e.g. let monitoring scrape your pods):

  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring

Practical rollout advice

  • Label namespaces and pods consistently: policies select on labels, so a labeling convention is a prerequisite.
  • Roll out in audit/log mode first (Cilium and Calico support this) so you see what would be blocked before you actually block it.
  • Start with default-deny ingress per namespace, add allow rules, then tackle egress once stable.
  • Test from inside the cluster: kubectl run tmp --rm -it --image=nicolaka/netshoot and try to reach things that should and shouldn't be reachable.

Where it fits in defense-in-depth

Network Policies are one layer. Combine them with:

  • RBAC for the API server (who can do what).
  • Pod Security Standards (what containers can do on the node).
  • A service mesh for mTLS and L7 policy if you need it (Istio/Linkerd/Consul comparison).

Network Policies handle L3/L4 segmentation cheaply and natively, they should be the baseline on every real cluster. See the devsecops-starter-kit for where this sits in a hardened setup.

The short version

  • Default Kubernetes networking is flat, every pod can reach every pod.
  • Confirm your CNI (Calico/Cilium) actually enforces NetworkPolicy.
  • Adopt default-deny, then allow specific paths, zero trust at L3/L4.
  • Always allow DNS when you restrict egress.
  • Roll out in audit mode first, label consistently, test from inside.

Hardening Kubernetes clusters is part of my DevSecOps consulting, reach out.

Share:LinkedInXWhatsApp

Related articles

Reactions & comments