7.3 NetworkPolicies, Zero-Trust Segmentation & the Gateway API

Key Takeaways

  • Kubernetes networking is allow-all by default; a Pod becomes isolated only once at least one NetworkPolicy selects it, and then only for the policyTypes listed.
  • NetworkPolicy rules are purely additive allow rules — the API has no deny rule, so isolation is achieved by writing a default-deny policy first.
  • podSelector, namespaceSelector, and ipBlock choose traffic sources and destinations; combining podSelector and namespaceSelector in one rule element means AND, while separate list elements mean OR.
  • Egress policies that block DNS break every workload in the namespace, so a default-deny egress policy must always allow UDP and TCP port 53 to kube-dns.
  • The Gateway API is the role-oriented successor to Ingress, splitting infrastructure concerns (GatewayClass, Gateway) from application routing (HTTPRoute) and supporting protocols beyond HTTP.
Last updated: August 2026

7.3 NetworkPolicies, Zero-Trust Segmentation & the Gateway API

Quick Answer: A default Kubernetes cluster is flat and completely open — any Pod can reach any other Pod in any namespace. A NetworkPolicy changes that, but only for the Pods it selects and only for the directions it names. Policies contain allow rules only, so real segmentation starts with an explicit default-deny policy. Enforcement lives in the CNI plugin, not in Kubernetes. For inbound traffic, the Gateway API is the role-oriented successor to Ingress.

Section 7.1 introduced NetworkPolicy in outline. This section covers the semantics that actually decide whether a policy protects you or silently does nothing.


1. The Default Is Allow-All

Out of the box there is no network segmentation:

  [ frontend/prod ] ──✓──► [ database/prod ]
  [ intern-sandbox ] ─✓──► [ database/prod ]      ← also allowed by default
  [ any pod ]        ─✓──► [ any pod, any namespace ]

Namespaces are an RBAC and naming boundary. They are not a network boundary. A compromised Pod in a sandbox namespace can open a TCP connection to a production database unless a NetworkPolicy says otherwise.


2. The Two Rules That Govern Every Policy

Rule 1 — Selection creates isolation. A Pod is non-isolated until at least one NetworkPolicy selects it. Once any policy selects it, that Pod is isolated for the directions named in policyTypes, and only traffic explicitly allowed by some policy gets through.

Rule 2 — Policies are additive allow rules. There is no deny rule in the API. Multiple policies selecting the same Pod are unioned: if any policy allows the traffic, it is permitted. You cannot write "allow everything except X" — you enumerate what is allowed.

The consequence is the default-deny pattern. An empty podSelector: {} selects every Pod in the namespace, and omitting ingress: allows nothing:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}                 # every Pod in this namespace
  policyTypes: [Ingress, Egress]  # isolate both directions
  # no ingress or egress rules ⇒ nothing is permitted

With that in place you add narrow allow policies on top.


3. Selecting Traffic

Three peer selectors exist, and a subtle AND/OR distinction between them is a reliable exam item.

spec:
  podSelector:
    matchLabels: { app: database }
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector:                          # element 1 …
        matchLabels: { app: api }
      namespaceSelector:                    # … same element ⇒ AND
        matchLabels: { env: production }
    - ipBlock:                              # separate element ⇒ OR
        cidr: 10.20.0.0/16
        except: [10.20.7.0/24]
    ports:
    - protocol: TCP
      port: 5432
  • podSelector and namespaceSelector inside the same list element means "Pods labelled app: api that are in a namespace labelled env: production".
  • Two separate list elements are OR'd: this policy allows the api Pods or the CIDR range.
  • podSelector alone always means "in the policy's own namespace".
  • namespaceSelector: {} means "all namespaces".

4. Egress and the DNS Trap

Egress policies are where teams break their own clusters. Once a Pod is isolated for egress, all outbound traffic is blocked — including DNS lookups to CoreDNS. Every application then fails with name-resolution errors that look nothing like a firewall problem.

Always pair a default-deny egress with a DNS allowance:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: production
spec:
  podSelector: {}
  policyTypes: [Egress]
  egress:
  - to:
    - namespaceSelector:
        matchLabels: { kubernetes.io/metadata.name: kube-system }
      podSelector:
        matchLabels: { k8s-app: kube-dns }
    ports:
    - { protocol: UDP, port: 53 }
    - { protocol: TCP, port: 53 }

5. Enforcement Lives in the CNI

This is the point that most often catches people out in production. kube-apiserver happily accepts and stores a NetworkPolicy regardless of whether anything can enforce it. Enforcement is performed by the CNI plugin's data plane.

CNI pluginNetworkPolicy support
CalicoFull, plus its own extended GlobalNetworkPolicy
CiliumFull via eBPF, plus L7-aware CiliumNetworkPolicy
Weave NetSupported
FlannelNone — policies are accepted by the API and silently ignored

So kubectl get networkpolicies showing a tidy list proves nothing. On Flannel, every one of those policies is decorative. Verifying enforcement means actually attempting the connection from a test Pod.

Beyond L3/L4

Standard NetworkPolicy operates at layer 3/4 — IPs, ports, protocols. It cannot express "allow GET /health but deny POST /admin". Layer 7 authorisation requires a service mesh (covered in Chapter 9) or a CNI-specific policy CRD such as CiliumNetworkPolicy.


6. The Gateway API

NetworkPolicy governs east-west traffic between Pods. Ingress and the Gateway API govern north-south traffic entering the cluster.

Ingress worked, but it accumulated well-known limits: HTTP/HTTPS only, no standard way to express header-based routing or traffic splitting, and a proliferation of controller-specific annotations that made manifests non-portable. The Gateway API — developed by Kubernetes SIG-Network and now the strategic direction — fixes this by splitting one object into a role-oriented set:

ResourceOwned byPurpose
GatewayClassInfrastructure providerWhich implementation backs a Gateway (Envoy, NGINX, a cloud LB)
GatewayCluster operatorAn actual listener: ports, protocols, TLS certificates, allowed routes
HTTPRoute (also GRPCRoute, TCPRoute, TLSRoute)Application developerHostnames, paths, headers, backends, and weights
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: api-route
  namespace: production
spec:
  parentRefs:
  - name: public-gateway
    namespace: infra
  hostnames: ["api.example.com"]
  rules:
  - matches:
    - path: { type: PathPrefix, value: /v2 }
    backendRefs:
    - { name: api-v2, port: 80, weight: 90 }   # native traffic splitting
    - { name: api-v3, port: 80, weight: 10 }   # canary, no annotations
AspectIngressGateway API
ProtocolsHTTP/HTTPSHTTP, HTTPS, gRPC, TCP, TLS, UDP
Traffic splittingController annotationsNative weight field
Header/method matchingAnnotationsNative matches
Role separationOne object, one ownerThree objects, three owners
Cross-namespace routingAwkwardExplicit, with ReferenceGrant

Ingress is not deprecated and remains widely deployed, but new platform work is expected to target the Gateway API.

Test Your Knowledge

A cluster has no NetworkPolicy objects at all. What traffic is permitted between Pods?

A
B
C
D
Test Your Knowledge

A team applies a default-deny egress NetworkPolicy to a namespace and every application immediately fails with name-resolution errors. What was omitted?

A
B
C
D
Test Your Knowledge

Which Gateway API resource is intended to be owned by the application developer rather than the cluster operator?

A
B
C
D