Understanding Kubernetes Network Policies
Network Policies are the primary mechanism for controlling traffic flow within a Kubernetes cluster at the IP address and port level. They act as a firewall between pods, allowing you to define exactly which network connections are permitted. Without Network Policies, all pods in a cluster can freely communicate with each other—a wide-open default that poses serious security risks. By implementing well-structured Network Policies, you enforce the principle of least privilege at the network layer, drastically reducing the attack surface and limiting the blast radius of a compromised workload.
What is a Network Policy?
A Network Policy is a Kubernetes resource that defines how groups of pods are allowed to communicate with each other and with other network endpoints. It uses labels to select pods and specifies rules for Ingress (incoming traffic) and Egress (outgoing traffic). Each rule can further filter traffic based on source pods, destination pods, namespaces, IP CIDR blocks, protocols, and ports. Crucially, Network Policies are additive—if multiple policies apply to a pod, the union of their rules is allowed, and any traffic not explicitly allowed by at least one policy is denied. This behavior makes them perfect for building a zero-trust network model.
Why Network Policies Matter for Security Hardening
In a default Kubernetes installation, there are no restrictions on pod-to-pod communication. An attacker who gains a foothold in one container can scan, probe, and connect to every other service in the cluster. Network Policies allow you to:
- Isolate sensitive workloads such as databases, secrets managers, or payment services from the rest of the cluster.
- Enforce micro-segmentation by restricting traffic to only the specific pods that need it, regardless of network boundaries.
- Prevent lateral movement by denying all traffic by default and explicitly allowing only required paths.
- Comply with regulatory standards like PCI-DSS, HIPAA, or SOC2 that demand strict network controls and segmentation.
- Protect against compromised dependencies by limiting which external services a pod can reach.
They are an essential layer in a defense-in-depth strategy, complementing RBAC, pod security standards, and container runtime hardening.
How Network Policies Work
Core Concepts: Pod Selectors, Policy Types, and Rules
A Network Policy specification consists of three main parts:
- podSelector: A label selector that determines which pods the policy applies to. An empty selector (often written as
{}) selects all pods in the namespace. - policyTypes: An array that declares whether the policy includes
Ingressrules,Egressrules, or both. If omitted, Ingress rules are applied by default, and Egress rules are applied only if present. - ingress / egress: Lists of rules defining allowed traffic. Each rule can specify sources (for ingress) or destinations (for egress) using pod selectors, namespace selectors, or IP CIDR ranges, plus ports and protocols.
It’s important to understand that policies are enforced by the network plugin (CNI) in your cluster. Not all CNIs support Network Policies—Calico, Cilium, Weave Net, and Antrea do, while vanilla Flannel typically does not. Always verify that your cluster’s CNI provides the necessary implementation.
Creating Your First Network Policy
Prerequisites
Before you can apply a Network Policy, ensure:
- Your cluster is running a supported CNI with Network Policy enforcement enabled (e.g., Calico, Cilium, or Weave).
- You have
kubectlconfigured and the necessary RBAC permissions to create NetworkPolicy resources. - The pods you want to protect are already deployed with meaningful labels (e.g.,
app: backend,tier: database).
A Simple Deny-All Policy
The most secure baseline is to deny all traffic to a pod, then progressively allow only what is required. The following policy selects all pods in a namespace and applies both Ingress and Egress rules with no allowed traffic. Because no rules are defined, all traffic is implicitly denied.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all
namespace: production
spec:
podSelector: {} # Selects every pod in the namespace
policyTypes:
- Ingress
- Egress
# No ingress or egress rules specified → all traffic blocked
Apply it with kubectl apply -f deny-all.yaml. After this, pods in the production namespace cannot send or receive any traffic—even DNS or communication with the API server will be blocked unless explicitly allowed. In practice, you’ll layer more specific policies on top.
Allowing Traffic from Specific Pods
Suppose you have a backend API pod labeled app: backend that should only accept requests from a frontend pod with label app: frontend. The following policy allows inbound traffic on TCP port 8080 from any pod that matches the frontend label, while denying everything else.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: production
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Note that this policy only affects pods with app: backend. Other pods in the namespace remain unaffected unless additional policies apply. If a default deny-all policy exists, this rule opens exactly the specified path.
Restricting Traffic to a Namespace
Often you need to allow traffic from an entire namespace, such as a monitoring or logging namespace. Use a namespaceSelector to grant access to all pods within a namespace, optionally filtered by pod labels.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-monitoring-ns
namespace: production
spec:
podSelector:
matchLabels:
app: metrics-collector
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
team: observability
ports:
- protocol: TCP
port: 9100
This allows any pod in a namespace labeled team: observability to connect to the metrics-collector pod on port 9100. You can combine namespaceSelector and podSelector within the same from block to narrow access to specific pods inside that namespace (both conditions must match).
Combining Ingress and Egress Rules
A complete security posture often requires controlling both inbound and outbound traffic. The policy below isolates a database pod: it only allows ingress from a specific backend pod on port 5432, and only allows egress to a specific backup service and to the DNS service for name resolution.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: database-isolation
namespace: production
spec:
podSelector:
matchLabels:
app: postgres-db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: backend-api
ports:
- protocol: TCP
port: 5432
egress:
- to:
- podSelector:
matchLabels:
app: backup-service
ports:
- protocol: TCP
port: 9000
- to:
- namespaceSelector: {}
- podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
Notice the egress rule for DNS: it uses an empty namespaceSelector (all namespaces) combined with the pod selector for kube-dns, allowing the database to resolve names. Without this, the database would be completely isolated and unable to look up service endpoints.
Advanced Network Policy Scenarios
Using IP Block-Based Rules
Sometimes you need to allow or deny traffic based on IP CIDR ranges, for example to permit access from on-premise systems or restrict outbound connections to a specific external service. IP block rules work alongside pod and namespace selectors.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-cidr
namespace: production
spec:
podSelector:
matchLabels:
app: public-api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/24
except:
- 10.0.0.5/32 # optionally exclude a specific IP
ports:
- protocol: TCP
port: 443
ipBlock can also be used in egress rules to limit which external IP ranges a pod can contact, providing an additional layer of egress security.
Multi-Port and Protocol Rules
A single Network Policy can specify multiple ports and protocols for the same set of sources or destinations. This is common for services that expose both HTTP and metrics endpoints.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-multi-port
namespace: production
spec:
podSelector:
matchLabels:
app: web-server
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: load-balancer
ports:
- protocol: TCP
port: 80
- protocol: TCP
port: 8080
- protocol: TCP
port: 9100
You can also mix protocols like TCP and UDP within the same rule set. For each port entry, specify the protocol and the port number.
Stacking Policies for Complex Models
Kubernetes merges multiple Network Policies that apply to a pod. This allows you to create a base deny-all policy, then layer on per-service, per-team, or per-environment policies. For example, you might have:
- A global deny-all policy for a namespace.
- A policy allowing DNS egress for all pods.
- Service-specific ingress policies for each microservice.
- Namespace-level policies allowing monitoring traffic.
The resulting allowed traffic is the union of all rules. This composability is powerful but requires careful planning to avoid accidentally allowing unintended traffic.
Best Practices for Security Hardening
1. Start with a Default Deny-All Policy
For every namespace that contains sensitive workloads, deploy a catch-all Network Policy that denies both ingress and egress traffic. This shifts the cluster from an "allow-all" model to a "deny-all, allow-by-exception" model—the foundation of zero trust. Make sure to also allow DNS egress (as shown earlier) to prevent service disruption.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: sensitive-apps
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
2. Use Meaningful Labels and Consistent Labeling Schemes
Network Policies rely entirely on labels for pod selection. Establish a clear labeling strategy across your organization—common patterns include app, component, tier, team, and environment. Avoid ambiguous labels that could accidentally grant access. Document the label taxonomy and enforce it through admission controllers or CI/CD pipelines.
3. Prefer Pod-Level Selectors Over IP Blocks
Pod selectors are dynamic and automatically adapt to pod restarts and scaling. IP block rules are static and brittle, requiring manual updates when IP ranges change. Use IP blocks only when you must control traffic from or to endpoints outside the cluster (e.g., VPN gateways, external databases). For internal cluster traffic, stick to pod and namespace selectors.
4. Isolate Namespaces with Careful Selectors
When using namespaceSelector, always pair it with a podSelector if you want to restrict traffic to specific pods within that namespace. An empty pod selector alongside a namespace selector will allow traffic from all pods in the target namespace, which might be too broad. Write rules that are as specific as possible.
5. Always Allow Required Infrastructure Services
Common infrastructure dependencies like DNS (kube-dns/CoreDNS), the API server, and monitoring agents need explicit egress rules when default deny is in place. Failing to allow DNS will cause service discovery failures; blocking the API server can break liveness probes and leader election. Include these allowances in a base policy that applies to all pods.
6. Test Policies in a Staging Environment
Network Policies can break application connectivity in subtle ways. Use a staging cluster that mirrors production labels and network topology to test new policies before rolling them out. Tools like kubectl describe networkpolicy and network policy visualizers (e.g., the Cilium UI or Calico’s policy debugger) help validate rule effects.
7. Log and Monitor Network Policy Events
Many CNIs can log denied traffic flows. Enable this logging to detect misconfigured policies or active intrusion attempts. Integrate logs into your SIEM or monitoring stack. For example, Calico provides a Log action in policies and Cilium offers network visibility through Hubble. Monitoring denied connections gives you early warning of malicious activity.
8. Combine Network Policies with Pod Security Standards
Network Policies are one layer. Complement them with Kubernetes Pod Security Standards (or the older PodSecurityPolicy) to restrict host network access, privileged containers, and capabilities. A pod that can bind to host ports or run as root can bypass network policies. Use restricted or baseline pod security profiles in tandem with network isolation.
9. Version Control and Review Policies
Treat Network Policies as code: store them in a Git repository, apply them via CI/CD, and require peer review. This prevents ad-hoc modifications that open up the cluster. Use tools like kube-linter or OPA Gatekeeper to enforce that every namespace has a default deny policy and that all policies follow your labeling standards.
10. Use Policy Tiering (if supported by your CNI)
Advanced CNIs like Calico support policy tiers and priority ordering, letting you enforce global policies that take precedence over namespace-scoped ones. This allows platform teams to mandate certain rules (e.g., “all pods must allow DNS and monitoring”) regardless of namespace-level policies. Explore your CNI’s extensions to build defense-in-depth.
Conclusion
Network Policies are an indispensable tool for hardening Kubernetes clusters. They transform the default flat network into a tightly controlled, segmented environment where every connection must be explicitly authorized. By starting with a deny-all posture, labeling pods consistently, and layering precise ingress and egress rules, you can dramatically reduce the risk of lateral movement and data exfiltration. Combine Network Policies with a supported CNI, continuous testing, logging, and complementary security controls to build a robust, zero-trust networking model inside your cluster. The effort invested in crafting and maintaining these policies pays off in a more resilient, auditable, and secure platform.