The default Kubernetes network is flat and fully open. Every pod can initiate a connection to every other pod, in every namespace, on every port — the control plane assumes trust and leaves segmentation to you. That default is fine for a demo and catastrophic in production, because it means a single compromised pod can reach your database, your internal APIs, your metrics endpoints, and the cloud metadata service, all without crossing a single boundary. When attackers talk about "lateral movement" inside a cluster, this flat network is what they are moving across. NetworkPolicies are the built-in mechanism for turning that open network into a set of explicit, least-privilege paths. This guide walks through how they actually work and how to adopt them without an outage.
The prerequisite everyone forgets: your CNI must enforce them
A NetworkPolicy is just an API object. Kubernetes will happily accept one and report success even if nothing enforces it. Enforcement is the job of your Container Network Interface plugin, and not all of them do it. Calico, Cilium, and Antrea enforce NetworkPolicy; the basic flannel plugin does not. Before you write a single policy, confirm your CNI enforces them — otherwise you will build a false sense of security around rules that no component is applying.
How the default-deny switch works
The critical thing to understand is that NetworkPolicies are additive and default-allow until the first policy selects a pod. The moment any policy with a given policyTypes selects a pod, that direction flips to default-deny for that pod, and only explicitly allowed traffic passes. So the foundational move is a namespace-wide deny-all that flips the default:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: payments
spec:
podSelector: {} # selects every pod in the namespace
policyTypes:
- Ingress
- Egress
# no ingress/egress rules = nothing is allowed
With this in place, every pod in payments is isolated in both directions. Now you add back only the flows the application genuinely needs.
Allowing the traffic you actually need
Rules are built from selectors: podSelector (by label), namespaceSelector (by namespace label), and ipBlock (by CIDR). Here is a policy that lets the api pods receive traffic only from the frontend pods, and only on port 8080:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: payments
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Egress deserves equal attention and is more often neglected. Locking down egress is what stops a compromised pod from reaching the cloud metadata endpoint or exfiltrating data to an attacker-controlled host:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-egress
namespace: payments
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
The DNS gotcha that breaks everything
The number-one way teams break their cluster with egress policies is by forgetting DNS. When you flip on default-deny egress, pods can no longer reach kube-dns/CoreDNS, so every hostname lookup fails and the application appears "randomly" broken even though the destination itself is allowed. Always add an explicit egress allowance for DNS:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
A rollout order that avoids outages
- Label everything first. Policies select by label, so audit that your namespaces carry the built-in
kubernetes.io/metadata.namelabel and your workloads have consistentapplabels. - Start in audit, not enforce. Cilium and Calico both offer a way to log what would be denied before you actually deny it. Use it to discover the flows you forgot.
- Apply default-deny per namespace, then add allows. Never flip the whole cluster at once. Take one namespace, add the allow rules its apps need, verify, then move on.
- Add DNS egress before anything else. It is the dependency every workload shares.
- Watch for cross-namespace and control-plane traffic. Ingress controllers, metrics scrapers, and admission webhooks all cross boundaries you may not have mapped.
Where plain NetworkPolicy runs out
Standard NetworkPolicy operates at L3/L4 — IPs, ports, and pod labels. It cannot express "allow GET on /health but deny POST on /admin," and its ipBlock rules are awkward for egress to external FQDNs whose IPs rotate. For L7-aware rules, DNS-based egress, and cluster-wide policy, teams reach for CiliumNetworkPolicy or a service mesh. Use native NetworkPolicy as the always-on baseline and layer richer controls where a workload's risk justifies the operational cost.
Quick reference
| Goal | Mechanism |
|---|---|
| Isolate a namespace | podSelector: {} with both policyTypes |
| Allow specific app-to-app | podSelector in from/to |
| Allow from another namespace | namespaceSelector (label the namespace) |
| Allow external CIDR | ipBlock with cidr and except |
| Keep DNS working | Explicit UDP/TCP 53 egress to kube-system |
| L7 / FQDN egress | CNI-specific policy or service mesh |
How Safeguard helps
NetworkPolicies are infrastructure-as-code, and the failure mode is subtle: a policy that looks restrictive but leaves an unlabeled pod fully exposed, or an egress rule that forgot to deny the metadata endpoint. Safeguard's IaC scanning analyzes your policy manifests and flags namespaces with no default-deny, workloads no policy selects, and egress rules that allow reaching sensitive destinations — before they merge. Because network exposure only matters in the context of what an image can do once reached, container security scanning ties a pod's reachable network paths to the vulnerabilities inside it, and Griffin AI prioritizes a CVE that sits on an internet-reachable, under-segmented workload over one that is boxed in by a tight policy. Teams comparing full cloud-native security suites can see how the approaches differ in Safeguard vs Wiz.
A flat network is an attacker's best friend — close it methodically. Create a free Safeguard account or read the documentation to validate your network posture as code.