Skip to content

NetworkPolicy — Kubernetes Firewall Rules

Reference: https://kubernetes.io/docs/concepts/services-networking/network-policies/


What is a NetworkPolicy?

NetworkPolicy is Kubernetes' firewall. Without one, all pods can talk to all other pods freely — no restrictions. Once you create a NetworkPolicy targeting a pod, it becomes "selected" and only traffic explicitly allowed in the policy is permitted. Everything else is denied.

The key model:

NetworkPolicy selects a pod via podSelector
    → controls Ingress (traffic INTO the pod)
    → controls Egress (traffic OUT OF the pod)
    → anything not explicitly allowed is denied once a policy selects the pod

Without any NetworkPolicy selecting a pod: all traffic allowed. With a NetworkPolicy selecting a pod: only what you declare is allowed.


The Full Structure

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-app-network-policy
  namespace: default
spec:
  podSelector:              # which pods this policy applies TO
    matchLabels:
      app: my-app
  policyTypes:
  - Ingress                 # restrict incoming traffic
  - Egress                  # restrict outgoing traffic
  ingress:
  - from:
    - podSelector:          # allow traffic FROM pods matching this label
        matchLabels:
          app: trusted
  egress:
  - to:
    - podSelector: {}       # allow outgoing traffic TO any pod

policyTypes — The Implicit Deny Mechanism

Declaring a type in policyTypes activates restriction for that direction. If you only declare Ingress, egress remains unrestricted:

policyTypes declared Ingress Egress
Neither All allowed All allowed
[Ingress] Restricted to what you define Still fully open
[Egress] Still fully open Restricted to what you define
[Ingress, Egress] Restricted Restricted

You do not write explicit deny rules. Kubernetes denies everything not in the allow list automatically once the type is declared.


podSelector — Three Different Contexts

This field means different things depending on where it appears:

Where What it selects
spec.podSelector The pods this policy applies TO (the "target")
ingress[].from[].podSelector Pods that are allowed to send traffic IN
egress[].to[].podSelector Pods that are allowed to receive traffic OUT
spec:
  podSelector:              # policy targets pods with app=my-app
    matchLabels:
      app: my-app
  ingress:
  - from:
    - podSelector:          # allow traffic FROM pods with app=trusted
        matchLabels:
          app: trusted

OR Logic — Rules Are OR'd, Not AND'd

Multiple ingress from rules (or egress to rules) are OR'd. Traffic is allowed if any rule matches.

This is a common bug:

# BROKEN — trying to say "only trusted pods"
ingress:
- from:
  - podSelector: {}             # allows ALL pods (empty = match everything)
- from:
  - podSelector:
      matchLabels:
        app: trusted            # dead code — {} already matched everything

podSelector: {} = match all pods. Once that rule exists, the app=trusted rule is completely redundant — {} already includes trusted pods and every other pod too.

Correct — only app=trusted pods:

ingress:
- from:
  - podSelector:
      matchLabels:
        app: trusted            # only this — nothing else

Correct — all pods (and want to be explicit):

ingress:
- from:
  - podSelector: {}             # matches every pod — just this rule

The confusion in the notes was thinking "more specific overrides generic" — it doesn't. OR means the most permissive rule wins.


Selector vs List Position

Note the difference between AND and OR in ingress from rules:

# OR — two separate from entries
ingress:
- from:
  - podSelector:
      matchLabels:
        app: backend
- from:
  - namespaceSelector:
      matchLabels:
        name: monitoring
→ Allow from app=backend pods OR from the monitoring namespace.

# AND — same from entry, multiple selectors
ingress:
- from:
  - podSelector:
      matchLabels:
        app: backend
    namespaceSelector:          # note: same list item, not separate
      matchLabels:
        name: production
→ Allow from app=backend pods AND only if they're in the production namespace.

The indentation matters — podSelector and namespaceSelector under the same - list item = AND. Separate - items = OR.


The Exercise YAML — Corrected

Requirements: only app=trusted pods can send traffic in; my-app can send traffic out to any pod; deny everything else.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-app-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: trusted          # ONLY trusted pods — no {} here
  egress:
  - to:
    - podSelector: {}           # can reach any pod

What the notes had wrong: Two ingress from rules — one with {} and one with app=trusted. The {} made app=trusted dead code and allowed everything in, contradicting the requirements.


Most Common Failure: Label Mismatch

If the policy applies but traffic still doesn't work as expected, 90% of the time it's a label mismatch. The podSelector in the policy doesn't match the actual labels on the pods.

# Check what labels the target pods actually have
kubectl get pods --show-labels -n default

# Describe the policy to see what it's selecting
kubectl describe networkpolicy my-app-network-policy

If kubectl describe shows Pod Selector: app=my-app but the pods have label run=my-app (because they were created with kubectl run), nothing is selected and the policy does nothing.


Quick Reference

# Create from file
kubectl apply -f my-app-network-policy.yml

# View policies in namespace
kubectl get networkpolicy
kubectl get netpol       # short name

# Describe (shows selectors, rules in human-readable form)
kubectl describe networkpolicy my-app-network-policy

# Test: does a pod have network access to another?
# From a test pod, try curl/wget to the target service
kubectl run test-pod --image=busybox --rm -it --restart=Never -- wget -qO- http://my-service

Common selectors reference

# Allow from ALL pods in the same namespace
- podSelector: {}

# Allow from pods with a specific label
- podSelector:
    matchLabels:
      app: trusted

# Allow from a specific namespace
- namespaceSelector:
    matchLabels:
      name: monitoring

# Allow from specific pods in a specific namespace (AND)
- podSelector:
    matchLabels:
      app: scraper
  namespaceSelector:
    matchLabels:
      name: monitoring

# Allow from external IP range
- ipBlock:
    cidr: 10.0.0.0/8
    except:
    - 10.0.0.0/24      # exclude this subnet