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:
Correct — all pods (and want to be explicit):
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
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
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