Asterrr's Handbook

Network policies

Default-deny NetworkPolicies, selecting peers by Pod labels, namespace labels and CIDRs, the AND vs OR trap in from and to, and keeping DNS working when you lock down egress.

Exam tasks: 3.2 (define and enforce Network Policies), with 5.5 when a policy blocks too much

The decision: which Pods does this policy select, which direction does it isolate, and exactly which peers and ports should still get through?

How enforcement works

  • Pods start non-isolated: everything is allowed. A Pod becomes isolated for a direction only when a policy selects it and lists that direction in policyTypes.
  • Policies are allow-lists and additive. The result is the union of all rules that apply. There's no deny rule and no ordering, so one permissive policy can't be overridden by a stricter one.
  • Policies are namespaced and select Pods in their own namespace only (podSelector).
  • Replies to an allowed connection are allowed automatically. You don't write a return rule.
  • The CNI plugin enforces them. On a plugin without support (plain Flannel), policies are accepted and do nothing.

Policy applied, nothing changed

If a correct-looking policy has no effect at all, check that the cluster's CNI plugin enforces NetworkPolicy and that podSelector really matches the target Pods' labels (k get pods --show-labels). An empty podSelector: {} selects every Pod in the namespace, which is often what a default deny needs.

The baseline: default deny

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: vault-svc
spec:
  podSelector: {}          # every Pod in vault-svc
  policyTypes:
  - Ingress
  - Egress                 # no ingress or egress rules listed, so nothing is allowed
  • Ingress only: list just Ingress. Egress only: just Egress.
  • If you omit policyTypes, it defaults to Ingress, plus Egress only if the policy has egress rules.
  • "Allow all ingress" is ingress: [{}]: one empty rule matches every source.

Selecting peers: AND vs OR

The single most tested detail. Each item in from (or to) is a separate peer, and items are ORed. Selectors inside the same item are ANDed.

ingress:
- from:
  - namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: storefront
    podSelector:                 # no dash: same item as namespaceSelector
      matchLabels:
        tier: api

Only Pods labelled tier=api in namespace storefront.

PeerMatches
podSelector alonePods in the policy's namespace with those labels
namespaceSelector aloneAll Pods in namespaces with those labels
Both in one itemPods with those labels in namespaces with those labels
ipBlock (with optional except)CIDRs, for traffic leaving or entering the cluster. Pod IPs are ephemeral, so use selectors for Pods

Exam signal

Every namespace carries the immutable label kubernetes.io/metadata.name: <name>. Use it to select a namespace by name instead of adding your own labels, unless the task tells you which label to use.

Ports

  ports:
  - protocol: TCP
    port: 5432
  - protocol: TCP
    port: 30000
    endPort: 30100      # a range
  - port: metrics      # a named containerPort on the target Pod
  • A rule with from and ports needs both to match. A rule with only ports allows those ports from anywhere.
  • Ports are the Pod's port (the targetPort), not the Service port. Policies act after the Service IP is translated.

Egress and DNS

Once you isolate egress, Pods can't resolve names, because CoreDNS is just another set of Pods. Allow it explicitly:

  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

Egress allowed to the database, app still broken

A policy that allows egress only to db on 5432 breaks an app that connects to db.data.svc.cluster.local, because the DNS lookup is dropped first. Symptoms look like a database outage. Add the DNS rule (UDP and TCP 53) whenever you write egress rules.

Testing a policy

k -n vault-svc get netpol
k -n vault-svc describe netpol allow-api      # shows the parsed selectors, check AND vs OR here
k -n storefront run t --rm -it --restart=Never --image=busybox:1.36 -l tier=api -- \
  wget -qO- -T 2 http://keeper.vault-svc:8200   # allowed peer: should answer
k -n storefront run t2 --rm -it --restart=Never --image=busybox:1.36 -- \
  wget -qO- -T 2 http://keeper.vault-svc:8200   # no label: should time out
Additive
Policies only add allowed traffic. There is no deny rule and no precedence.
podSelector: {}
Selects every Pod in the policy's namespace.
UDP+TCP 53
What egress must allow to kube-dns for name resolution.
Ingress
Default policyTypes when unset and no egress rules exist.

Scenarios

Scenario
Namespace ledger has a default-deny ingress policy. You must let Pods labelled role=reporter in namespace analytics reach Pods labelled app=books in ledger, and nothing else from analytics. Which from block is correct?
Scenario
A team adds an egress policy to namespace billing that allows only TCP 443 to ipBlock 0.0.0.0/0. Immediately, calls to https://payments.example.net fail with 'bad address', while curl to the provider's IP works. What is the fix?
Scenario
Namespace web has two policies selecting app=front: one allows ingress only from app=lb on port 80, the other allows ingress from all Pods in namespace monitoring on any port. A Pod in monitoring connects to app=front on port 9100. What happens?

Drill

kubectl config use-context lab-net

Namespace vault-svc runs Pods labelled app=keeper listening on TCP 8200. Make sure that:

  1. All ingress to every Pod in vault-svc is denied by default (policy deny-ingress).
  2. Pods labelled tier=api in namespace storefront can reach app=keeper Pods on TCP 8200 only (policy allow-api).

Further reading

On this page