HackBlue

Tools · Containers and Kubernetes security · 06 / 20

Kubernetes NetworkPolicy tutorial: default deny and allowing only what you need

Understand that pods accept traffic from anywhere by default, write a default-deny policy, allow specific ingress and DNS egress, and test the result.

11 min read · practice only with authorization

Open by default

Without any NetworkPolicy, every pod can reach every other pod in the cluster on any port. A NetworkPolicy selects pods with a podSelector and lists allowed ingress and egress. Once a pod is selected by any policy for a direction, only traffic matching some policy is allowed in that direction. Policies are additive allow lists.

Policies only work if the cluster’s network plugin enforces them. Calico, Cilium, and most managed CNIs do; the basic kindnet plugin historically did not, so pick a CNI that does for your lab.

Default deny plus explicit allows

The first policy below selects every pod in the namespace and allows nothing, which denies all ingress and egress. The second lets frontend pods reach api pods on port 8080. The third lets all pods resolve DNS through kube-dns, which almost every workload needs once egress is denied.

Snippet
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: shop }
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: frontend-to-api, namespace: shop }
spec:
  podSelector: { matchLabels: { app: api } }
  ingress:
    - from: [ { podSelector: { matchLabels: { app: frontend } } } ]
      ports: [ { protocol: TCP, port: 8080 } ]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns, namespace: shop }
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 } ]

Lab: prove it with curl

Create a kind cluster with Calico or a minikube cluster with --cni=calico. Deploy api and frontend pods plus a stranger pod with a different label. Before policies, curl from both to the api service succeeds. Apply default-deny and watch both fail. Apply frontend-to-api and allow-dns and confirm only the frontend succeeds. Note that frontend also needs an egress allow to api if egress is denied; add it and test again.

Snippet
kubectl -n shop exec deploy/frontend -- wget -qO- -T 3 http://api:8080/health
kubectl -n shop exec deploy/stranger -- wget -qO- -T 3 http://api:8080/health   # expect timeout

Pitfalls

Forgetting DNS egress breaks everything in confusing ways. Putting namespaceSelector and podSelector in separate list items means OR, while placing them in the same item means AND. Policies do not apply to traffic from the node itself in many CNIs, and they do not encrypt anything.

Diagram

Rolling out network policy

  1. Map traffic

    List which apps talk to which, on which ports.

  2. Default deny

    Apply an empty-selector deny policy per namespace.

  3. Allow DNS

    Permit egress to kube-dns on port 53.

  4. Allow flows

    Add one policy per required connection and test each with curl.

Check yourself

1. With no NetworkPolicy objects in a cluster, which traffic between pods is allowed?

2. After applying default deny for egress, every app fails to reach services by name. What is missing?

3. Policies are applied but have no effect. What is a likely cause?