Every pod in a Kubernetes cluster can reach every other pod, and the internet, until someone writes a policy that says otherwise. NetworkPolicy is the built-in way to change that, but it is enforced only by the network plugin, it is namespaced, it has no deny rules and no logging, and many clusters run with a handful of policies that look reassuring and change little. This guide explains how the NetworkPolicy API actually behaves, how the newer cluster-scoped ClusterNetworkPolicy (the successor to AdminNetworkPolicy) fits above it, and how Cilium, Calico and the managed services enforce both. It is written for platform engineers, cluster administrators and security architects who need default deny and egress control that holds up in production.
Key takeaways
- A policy without an enforcing plugin does nothing. The API server accepts NetworkPolicy objects even when the CNI ignores them, so confirm enforcement before trusting any policy.
- Isolation is per pod and per direction. A pod is unrestricted until a policy selecting it lists Ingress or Egress in policyTypes; after that only the union of allow rules applies, and both ends of a connection must allow it.
- Default deny is a namespace habit, not a cluster setting. Plain NetworkPolicy cannot set cluster-wide defaults; ClusterNetworkPolicy (Baseline tier) or a CNI global policy is how platform teams do that.
- Egress is where the risk sits. Most clusters filter ingress and leave egress open, which leaves the cloud metadata endpoint, the API server and the internet reachable from a compromised pod.
- Stage, observe, then enforce. Cilium policy audit mode and Calico staged policies let you see what a policy would drop before it drops it; flow visibility is also the only way to prove a policy works.
How the NetworkPolicy API behaves
NetworkPolicy (networking.k8s.io/v1) is a namespaced allow list for layer 4 traffic (TCP, UDP and, where the plugin supports it, SCTP). The Kubernetes documentation is explicit that behaviour for ICMP, ARP and other protocols is undefined and varies by plugin. The rules below cause most of the confusion in real clusters.
- Selection creates isolation: by default a pod is non-isolated for both ingress and egress. It becomes isolated for a direction when any policy whose
podSelectormatches it lists that direction inpolicyTypes. An emptypodSelector: {}selects every pod in the namespace. - policyTypes defaults: if
policyTypesis omitted, Ingress is always set and Egress is set only when the policy has egress rules. A policy written to restrict egress without the field, and without egress rules, restricts nothing outbound. - Rules are additive: there is no ordering and no deny. The allowed set is the union of every applicable policy, so one broad allow (for example an allow-all ingress policy) cannot be cancelled by a narrower one.
- Both ends must agree: a pod-to-pod connection needs the source pod's egress and the destination pod's ingress to allow it. Reply traffic for an allowed connection is allowed implicitly.
- AND versus OR selectors: one
fromelement containing bothnamespaceSelectorandpodSelectormeans "these pods in these namespaces". Two separate elements mean "these pods in this namespace, or any pod in those namespaces". A single YAML dash changes the meaning;kubectl describe networkpolicyshows how the API interpreted it. - Namespaces by label only: policies cannot name a namespace, but the control plane sets the immutable label
kubernetes.io/metadata.nameon every namespace, which is safer than team-applied labels that a namespace owner could change. - ipBlock is for external ranges: pod IPs are ephemeral, and whether source NAT or load balancer rewriting happens before or after policy evaluation is undefined, so the "source IP" seen at ingress may be a node or load balancer address.
- Port ranges:
endPorthas been stable since Kubernetes 1.25; it must be numeric and at leastport. A plugin that does not support it enforces only the singleport. - Node traffic is always allowed: an isolated pod still accepts connections from its own node, and a pod cannot block access to itself or loopback.
What NetworkPolicy cannot do
The upstream documentation lists the gaps, and each one decides which tool you need on top:
| Gap in NetworkPolicy v1 | Consequence | Where it is solved |
|---|---|---|
| No cluster-wide or default policies | Every namespace needs its own default deny; new namespaces start open | ClusterNetworkPolicy (Baseline or Admin tier), CiliumClusterwideNetworkPolicy, Calico GlobalNetworkPolicy, or admission control that injects defaults |
| No explicit deny | A platform team cannot block a destination that an application team allows | ClusterNetworkPolicy Admin tier Deny, Cilium deny policies, Calico Deny in a higher tier |
| No logging of allowed or blocked connections | No evidence a policy works; silent breakage during rollout | Hubble (Cilium), Calico flow logs or Log action, managed service policy logging |
| No FQDN or layer 7 rules | Egress to SaaS APIs needs fragile IP ranges | Cilium toFQDNs and L7 rules, ClusterNetworkPolicy domainNames peer, egress gateway or proxy |
| No TLS or identity of the caller | Policy trusts labels and IPs, not workload identity | Service mesh mutual TLS and authorisation policy |
| No node-identity targeting, no service names | Nodes only as CIDRs; services only through pod labels | CNI host policies; label-based rules |
Default deny and the baseline policy set
The Kubernetes security checklist asks for ingress and egress policies on all workloads and a default policy in each namespace that selects all pods and denies everything. In practice every namespace needs a small, predictable set of policies so that application teams only write the allows that are specific to them.
| Policy | Selects | Purpose | Watch out for |
|---|---|---|---|
| default-deny-all | podSelector: {}, policyTypes Ingress and Egress, no rules | Isolates every pod in both directions | Also blocks DNS; pods created before the plugin processes it may briefly start unprotected |
| allow-dns | All pods; egress to the cluster DNS pods (namespace label kubernetes.io/metadata.name: kube-system plus the DNS pod label) on UDP and TCP 53 | Restores name resolution | Node-local DNS caches listen on a link-local address and need an ipBlock rule instead |
| allow-same-namespace (optional) | All pods; ingress from podSelector: {} | Lets a team start with namespace-level segmentation | Becomes permanent if nobody tightens it |
| allow-ingress-controller | Front-end pods; ingress from the ingress controller namespace on the service port | North-south entry only through the controller | Avoid opening the same ports to all namespaces |
| allow-monitoring | All pods; ingress from the monitoring namespace on the metrics port | Keeps scraping working | Scope to the metrics port, not all ports |
| app-specific allows | Named workloads | The real application flows, with ports | Prefer pod plus namespace selectors over ipBlock |
Ship the baseline set through the same pipeline that creates namespaces, so a namespace never exists without it. The Kubernetes Namespace Onboarding Checklist covers the surrounding steps (quotas, RBAC, Pod Security labels). Where a policy engine is in place, a validating rule that rejects namespaces without a default deny closes the gap the API leaves; see the Kubernetes Admission Control Guide.
Egress control
Egress is where network policy pays for itself after a compromise: it decides whether a pod running attacker code can scan the cluster (MITRE ATT&CK T1046 Network Service Discovery), reach other services to exploit them (T1210 Exploitation of Remote Services), read credentials from the cloud instance metadata endpoint (T1552.005 Cloud Instance Metadata API) or connect out to mining pools and staging servers (T1496 Resource Hijacking). Treat these destinations deliberately:
- Cloud metadata (169.254.169.254): block it for workloads that do not need it. Where workload identity depends on it, allow it narrowly: Google documents that GKE clusters using Workload Identity Federation with Dataplane V2 need egress to
169.254.169.254/32on ports 80 and 8080, and AWS notes that pods using node instance role credentials or IMDS may need an explicit allow. See the EC2 Instance Metadata Service Hardening Guide. - The Kubernetes API server: most application pods never need it. Cilium has a
kube-apiserverentity for this; with plain NetworkPolicy you allow the API server endpoint addresses as an ipBlock only for controllers and operators that need it. - DNS: allow only the cluster DNS service. Pods that can query any external resolver can tunnel data over DNS (T1071.004). See the CoreDNS Security guide.
- Internet destinations: plain NetworkPolicy only understands CIDRs. For SaaS and package registries, use FQDN-aware rules (Cilium
toFQDNswithmatchNameormatchPattern, which learns IPs through the agent's DNS proxy, or the ClusterNetworkPolicydomainNamespeer) or send traffic through an egress gateway or proxy. The Cloud Egress Filtering guide covers the layer outside the cluster. - Other clusters and data stores: allow databases and message brokers by CIDR and port, and record the owner of each ipBlock so stale ranges are removed.
Cluster-wide policy: AdminNetworkPolicy to ClusterNetworkPolicy
The SIG Network Policy API project (policy.networking.k8s.io, delivered as CRDs, not core Kubernetes) adds policy for the cluster administrator persona. Its history matters because tutorials still show the old resources:
- v1alpha1 (releases 0.1.x): AdminNetworkPolicy (ANP) and BaselineAdminNetworkPolicy (BANP), with actions Allow, Deny and Pass.
- v1alpha2 (release 0.2.0): the two were merged into a single ClusterNetworkPolicy (CNP) with a
tierfield ofAdminorBaseline; Allow was renamedAcceptand theportsfield was replaced byprotocols. - v1beta1 (release 0.3.0): CNP graduated to v1beta1 as the storage version, with v1alpha2 still served and an identical schema. As checked on 11 November 2026 it remains a beta CRD, so pin the bundle version you install and expect changes before it is stable.
Evaluation runs in three tiers: Admin, then namespace NetworkPolicy, then Baseline. Within a tier, a lower priority (0 to 1000) wins. Admin Deny and Accept are final and cannot be overridden by namespace owners; Pass hands the decision down to NetworkPolicy. Baseline rules apply only where no namespace policy decided, which is how a platform team flips the cluster to default deny while still letting teams open their own flows. Unlike NetworkPolicy, CNP rules are read as written and create no implicit isolation, and host-networked pods are not selected.
| Need | Tier and action | Example |
|---|---|---|
| Guardrail nobody can override | Admin, Deny | Block all pods from the metadata endpoint except an identity namespace |
| Platform traffic that must always work | Admin, Accept | Egress to cluster DNS; ingress from the monitoring namespace |
| Delegate a decision to namespace owners | Admin, Pass | Traffic within a tenant's namespaces |
| Cluster-wide default deny that teams can open | Baseline, Deny | Deny all ingress from other namespaces unless a NetworkPolicy allows it |
Implementation support is uneven. The project's implementations page (updated 21 April 2026) lists ClusterNetworkPolicy in kube-network-policies, experimental support in Kube-OVN and standard support in Calico 3.32, while OVN-Kubernetes, Antrea, Kube-OVN and earlier Calico releases implement the v1alpha1 ANP resources and Cilium has a tracking issue. Amazon EKS ships its own ClusterNetworkPolicy in a different API group (networking.k8s.aws/v1alpha1) with the same Admin and Baseline tier idea. Check the exact API group and version your plugin reconciles before writing policies.
CNI enforcement compared
| Plugin or service | NetworkPolicy v1 | Cluster-wide and deny | Beyond layer 4 | Observability and safe rollout |
|---|---|---|---|---|
| Cilium (eBPF) | Yes | CiliumClusterwideNetworkPolicy; deny policies (since 1.9) take precedence over any allow; enforcement mode default, always or never | FQDN (toFQDNs), L7 HTTP rules, entities such as world and kube-apiserver | Hubble flows with policy verdicts; policy audit mode (policy-audit-mode, or per endpoint) logs would-be drops as AUDITED |
| Calico | Yes | GlobalNetworkPolicy, tiers ordered by order with Pass; ClusterNetworkPolicy placed in the kube-admin (order 1000) and kube-baseline (order 10000000) tiers | Host endpoint policy; network sets | Staged policies (StagedNetworkPolicy, StagedGlobalNetworkPolicy, StagedKubernetesNetworkPolicy) preview impact without enforcing |
| Amazon EKS (VPC CNI) | Yes, VPC CNI 1.21.0 or later for standard and admin policies | EKS ClusterNetworkPolicy, Admin and Baseline tiers | No | Linux EC2 nodes only (not Fargate or Windows); 24 port and protocol combinations per CIDR |
| Google GKE | Yes | Via Dataplane V2 (Cilium based) | Depends on edition | Enforcement cannot be disabled on Autopilot or Dataplane V2; Standard clusters on the older path need the add-on plus node enforcement, which recreates nodes |
| Azure AKS | Yes | Cilium engine recommended; Azure NPM is being retired | Cilium engine adds FQDN and L7 | Azure NPM support ends on Windows nodes from 30 September 2026 and on Linux nodes from 30 September 2028, as stated by Microsoft |
Two plugin caveats catch teams out. GKE Dataplane V2 does not allow ipBlock to target pod or service IPs, and AKS-managed Calico supports only networking.k8s.io policies, not projectcalico.org/v3. See the Amazon EKS Security Hardening Guide and the Azure Kubernetes Service Security Hardening Guide for the wider platform settings.
Choosing an approach
- Single team, small cluster: plain NetworkPolicy with the baseline set per namespace, generated from a template, is enough. Add an admission rule that refuses namespaces without default deny.
- Multi-tenant or regulated cluster: you need guardrails application teams cannot override. Use ClusterNetworkPolicy where your plugin supports it, otherwise the CNI's own global and deny policies, and accept the portability cost.
- Heavy SaaS egress: FQDN-aware policy or an egress gateway beats maintaining IP lists; CIDR rules for cloud provider APIs drift constantly.
- Need caller identity, encryption or HTTP-level rules: add a service mesh for mutual TLS and authorisation, and keep network policy as the layer that still holds if a sidecar is bypassed. See the Service Mesh Security Guide.
- Portability versus capability: CNI-specific CRDs are richer today; ClusterNetworkPolicy is the portable direction but still beta. Keep namespace-level rules in NetworkPolicy v1 so they survive a plugin change.
Where identity fits
Network policy decides which pods can talk; it does not know which service account is calling. Labels are its identity, so whoever can set pod or namespace labels can change what a policy matches. Restrict who can edit labels on namespaces through RBAC, prefer the immutable kubernetes.io/metadata.name label in selectors, and pair egress rules with short-lived workload credentials so a reachable endpoint still demands a valid token. The Kubernetes NHI Security Guide covers service account tokens and workload identity, and the Kubernetes RBAC Guide covers who may create or edit NetworkPolicy objects in the first place.
Rolling out in phases
- Confirm enforcement: check the plugin and version, then prove it by applying a deny policy in a test namespace and confirming a connection fails.
- Observe: collect flows for two to four weeks (Hubble, Calico flow logs, cloud flow logs) to build the real dependency map, including batch jobs and month-end processes.
- Platform guardrails first: apply Admin-tier or global rules for DNS, monitoring and metadata blocking, which affect every namespace consistently.
- Stage default deny per namespace: run Cilium audit mode or Calico staged policies for the namespace and fix every would-be drop that is legitimate.
- Enforce in rings: start with low-risk namespaces, then production tiers, with the application owner on hand. Avoid changing policies during active traffic peaks: the effect on existing connections is implementation defined.
- Add egress restrictions last: egress breaks more things than ingress; add explicit allows for registries, SaaS APIs and data stores.
- Roll back cleanly: keep policies in Git and deploy them through GitOps so rollback is a revert; an emergency allow-all policy in a namespace restores traffic because rules are additive, and should expire with a ticket.
Detection and measurement
Because NetworkPolicy itself logs nothing, measurement depends on the plugin and on the Kubernetes audit log. Watch for creation or deletion of NetworkPolicy, ClusterNetworkPolicy and CNI policy objects, label changes on namespaces, and namespaces created without a baseline (see the Kubernetes Audit Log Detection Guide). In the data plane, alert on denied egress to the metadata endpoint, to the API server and to unusual external ports, which are early signs of a compromised pod.
- Namespace coverage: percentage of namespaces with default deny in both directions (target 100 percent outside system namespaces).
- Workload coverage: percentage of pods selected by at least one ingress and one egress policy.
- Egress openness: number of policies allowing
0.0.0.0/0or theworldentity, trending down. - Metadata exposure: count of namespaces that can reach 169.254.169.254 without a documented need.
- Drop rate after change: denied flows per policy change in the first 24 hours, which shows whether staging is working.
- Policy drift: objects in the cluster that differ from Git.
Framework mapping
| Framework | Reference | How network policy supports it |
|---|---|---|
| NIST SP 800-53 Rev. 5 | SC-7 Boundary Protection; SC-7(5) Deny by Default, Allow by Exception; AC-4 Information Flow Enforcement; CM-7 Least Functionality; AU-12 Audit Record Generation | Default deny and explicit allows per workload; flow and audit logging |
| NIST CSF 2.0 | PR.IR-01; PR.PS-01; DE.CM-01 | Logical access between workloads; policy as managed configuration; monitored flows |
| CIS Controls v8.1 | Control 12 Network Infrastructure Management; Control 13 Network Monitoring and Defense | Segmentation inside the cluster; flow visibility and alerting |
| ISO/IEC 27001:2022 Annex A | A.8.20 Networks security; A.8.22 Segregation of networks | Namespaces and tiers as segregated zones |
| MITRE D3FEND | D3-NTF Network Traffic Filtering; D3-ITF Inbound Traffic Filtering; D3-OTF Outbound Traffic Filtering; D3-NI Network Isolation | The countermeasures network policy implements |
| MITRE ATT&CK | T1046; T1210; T1552.005; T1611; T1496 | Limits discovery, lateral movement, metadata credential theft, post-escape reach and resource hijacking |
Common failure patterns
- Policies on a plugin that ignores them: the objects exist, audits pass on paper, and nothing is filtered. Test enforcement after every CNI upgrade or migration.
- Ingress only: egress left open means a compromised pod reaches metadata, the API server and the internet. The Tesla Kubernetes cryptojacking incident shows how an exposed console plus unrestricted outbound access becomes credential theft and crypto mining.
- The YAML dash mistake: splitting
namespaceSelectorandpodSelectorinto twofromelements turns "these pods in that namespace" into "any pod in that namespace", silently widening access. - Default deny that forgets DNS: the rollout breaks everything, someone applies allow-all egress to recover, and it stays.
- Trusting mutable labels: a namespace owner adds the label a policy allows (for example
team=platform) and inherits access; usekubernetes.io/metadata.nameand restrict label edits. - Copying AdminNetworkPolicy tutorials: manifests written for v1alpha1 ANP fail or are ignored where the plugin only reconciles ClusterNetworkPolicy, or the reverse. See the Kubernetes Cluster Compromise Playbook for what to do when the guardrail was not there.
Practitioner checklist
- Confirm the CNI enforces NetworkPolicy (and which admin policy API and version it reconciles) with a live deny test.
- Deploy default deny for ingress and egress, plus allow-dns, in every non-system namespace through the namespace pipeline.
- Reject namespaces without a baseline at admission.
- Block egress to 169.254.169.254 except where workload identity needs it, and allow it narrowly there.
- Restrict API server egress to controllers and operators that need it.
- Use FQDN-aware rules or an egress gateway for internet destinations instead of broad CIDRs.
- Put non-negotiable guardrails in an Admin tier or CNI global deny policy that namespace owners cannot override.
- Stage every new default deny with audit mode or staged policies before enforcing it.
- Keep all policies in Git, deployed by GitOps, with drift alerts.
- Report namespace and workload coverage, open egress rules and metadata exposure monthly.
Standards and references
- Kubernetes: Network Policies
- Kubernetes: Security Checklist
- Kubernetes SIG Network: Network Policy API, ClusterNetworkPolicy overview
- Cilium: Network Policy documentation
- Calico: Stage, preview impacts and enforce policy
- AWS: Limit Pod traffic with Kubernetes network policies (Amazon EKS)
- Microsoft Learn: Secure traffic between pods by using network policies in AKS
- NIST: SP 800-53 Rev. 5
Related NHI Mgmt Group resources: Kubernetes Security Guide · Network Segmentation Guide · Service Mesh Security Guide · Microsegmentation Buyers Guide