Skip to content

Network Policies

NetworkPolicy controls pod-to-pod traffic at Layer 3 and Layer 4.

By default, many clusters allow broad lateral traffic. NetworkPolicy lets you move to explicit allow rules.

Cluster requirements

Your CNI must implement NetworkPolicy. If it does not, policy objects may apply successfully but have no effect.

The evaluation model (read this first)

The v1 NetworkPolicy API has no deny rules, no priorities, and no ordering (see AdminNetworkPolicy below for the cluster-scoped API that adds them). The entire model reduces to two rules:

  1. A pod not selected by any policy accepts everything. Isolation is opt-in, per direction: a pod becomes ingress-isolated the moment any policy with policyTypes: [Ingress] selects it (and likewise for egress).
  2. Once isolated, traffic is allowed if any policy allows it. Policies are purely additive - they union together. You can never write a policy that takes access away from what another policy grants; you can only avoid granting it.

This is why default-deny works the way it does: an empty podSelector: {} policy selects every pod in the namespace (making them all isolated) while allowing nothing. Every subsequent policy then punches specific holes.

One stray - widens the rule to everything

In a from/to list, separate list items are OR, but namespaceSelector and podSelector combined in a single item are AND. A stray - turns "pods with this label in that namespace" into "all pods in that namespace, plus pods with this label anywhere" - a real security bug that passes review easily.

Two scope limits are worth knowing before you rely on it:

  • Host-network pods are not subject to NetworkPolicy. A pod with hostNetwork: true uses the node's network namespace, so the CNI's per-pod enforcement point isn't in its path. Policies that select it will appear to apply and do nothing.
  • endPort covers port ranges. A rule can specify port: 8000 with endPort: 8100 to allow a contiguous range instead of listing every port, provided the CNI supports it.

AdminNetworkPolicy and BaselineAdminNetworkPolicy

The v1 NetworkPolicy API is namespace-scoped and additive by design, which makes it a poor fit for cluster-wide guardrails: a namespace owner can always grant themselves more ingress, and there is no way for a platform team to write a rule that a tenant cannot override. AdminNetworkPolicy (ANP) and BaselineAdminNetworkPolicy (BANP), from the policy.networking.k8s.io API group and implemented by Cilium and several other CNIs, exist to close exactly that gap.

The evaluation order is the point:

  1. AdminNetworkPolicy - cluster-scoped, evaluated before any NetworkPolicy, in ascending priority order (lower number wins). Actions are Allow, Deny, or Pass. An ANP Deny is a real deny that a tenant's NetworkPolicy cannot undo. Pass explicitly hands the decision down to the namespace-scoped layer.
  2. NetworkPolicy - the v1, namespace-scoped, additive model described above.
  3. BaselineAdminNetworkPolicy - a single cluster-scoped object named default, evaluated last, as the fallback for traffic that no ANP or NetworkPolicy decided. This is how you express "the cluster default is deny" without writing a default-deny policy in every namespace.
apiVersion: policy.networking.k8s.io/v1alpha1
kind: AdminNetworkPolicy
metadata:
  name: tenant-isolation
spec:
  priority: 10
  subject:
    namespaces:
      matchLabels:
        tenancy: shared
  egress:
    # Nobody in a shared-tenancy namespace reaches the cloud metadata endpoint.
    - name: deny-cloud-metadata
      action: Deny
      to:
        - networks:
            - 169.254.169.254/32
    # DNS to kube-system is always permitted, regardless of tenant policy.
    - name: allow-dns
      action: Allow
      to:
        - namespaces:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - portNumber:
            protocol: UDP
            port: 53
  ingress:
    - name: pass-to-namespace-policy
      action: Pass
      from:
        - namespaces: {}

ANP/BANP support varies by CNI and the API is still moving, so check your CNI's documentation for which version and which fields it implements before you design a guardrail around it. NetworkPolicy remains the portable baseline.

Trust model

graph LR
    subgraph payments namespace
        API[api pod]
        DB[db pod]
    end
    subgraph monitoring namespace
        PROM[prometheus pod]
    end
    subgraph frontend namespace
        WEB[web pod]
    end

    WEB -->|allowed: 8080| API
    PROM -->|allowed: 9090| API
    API -->|allowed: 5432| DB
    WEB -. blocked .-> DB

Define your trust boundaries per namespace first. Then build allow rules to match.

Default-deny foundation

Start sensitive namespaces with default-deny, then add explicit allow rules.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Allow app-to-app ingress

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - protocol: TCP
          port: 8080

Cross-namespace allow

Use namespaceSelector when source pods live in another namespace.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-monitoring-scrape
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoring
      ports:
        - protocol: TCP
          port: 9090

Egress and DNS

When egress restrictions are enabled, allow DNS explicitly or service discovery will break - this is the most common self-inflicted outage when adopting egress policies (see DNS and Service Discovery).

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

IP block rules

Use ipBlock to control traffic to or from external IP ranges. This is useful for allowing access from a corporate network or blocking cloud metadata endpoints.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-external-api
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 203.0.113.0/24
            except:
              - 203.0.113.10/32
      ports:
        - protocol: TCP
          port: 443

except lets you exclude specific IPs within a broader allowed range.

Blocking the cloud metadata endpoint

The link-local metadata endpoint at 169.254.169.254 hands out instance credentials, and reaching it from a compromised pod is a well-worn escalation path. Blocking it with v1 NetworkPolicy is awkward, because there are no deny rules: you cannot write "deny 169.254.169.254." What you can do is make the allowed egress range exclude it, which only works in a namespace that is already egress-isolated by a default-deny policy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-except-metadata
  namespace: payments
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 169.254.169.254/32
              - 169.254.0.0/16     # link-local generally
              - 10.0.0.0/8         # keep in-cluster/VPC traffic on explicit rules

Two caveats. First, this only holds if nothing else in the namespace grants broader egress - a single additive policy elsewhere with cidr: 0.0.0.0/0 and no except re-opens it. Second, this is precisely the case AdminNetworkPolicy handles better: a cluster-scoped ANP Deny for that CIDR cannot be undone by a tenant policy, which is why the ANP example above uses it.

Troubleshooting

kubectl get netpol -A
kubectl describe netpol allow-web-to-api -n payments
kubectl get pods -A --show-labels

Also validate policy behavior with test pods and explicit connectivity checks (curl, nc, or purpose-built policy tests).

Certification notes

  • NetworkPolicy is core CKS material and appears on CKA. Practice writing default-deny plus one allow rule from memory - the YAML shape (policyTypes, from vs ingress) is easy to fumble under time pressure.
  • Remember: policies select the pods they protect (spec.podSelector), not the traffic source. Exam questions exploit this reversal.
  • Know the AND/OR distinction in from items - it's a favorite trick.

Design guidance

  • define namespace trust boundaries first
  • apply default-deny early in new namespaces
  • make allow rules specific by label and port
  • keep policy manifests version-controlled and reviewed

Summary

NetworkPolicy is a core Kubernetes security control for lateral movement reduction. It should be standard in production clusters, especially in multi-team environments.

Further Reading