62.9 NetworkPolicy lab with Calico: default deny, allow rules and DNS
Why: the problem
Everything in this chapter so far rests on one fact from 62.1: every pod can reach every other pod. That is convenient, and it is also a security problem. If an attacker gets into the public storefront pod at a fintech like CRED, nothing stops them from connecting straight to the payments database pod. Kubernetes' answer is NetworkPolicy, a firewall for pods written in YAML. A NetworkPolicy selects pods with podSelector and lists allowed ingress (incoming) and egress (outgoing) traffic by pod labels, namespace labels, IP blocks and ports. The rule that confuses everyone: once any policy selects a pod for a direction, everything in that direction not explicitly allowed is denied. Policies only add allows; there are no deny rules in the standard API. Pods that no policy selects stay fully open. NetworkPolicy is only enforced if the CNI supports it. That is why we installed Calico in 62.1 instead of kindnet. On EKS the VPC CNI supports it when its network policy feature is enabled; Calico and Cilium also work. In this lesson we build a three-tier app (web, api, db), apply default deny for the namespace, open only web to api and api to db, fix the DNS breakage that default-deny egress causes, and test every path with a small script, the way you would prove it to a security auditor.
What: every term in plain English
- NetworkPolicy — A namespaced firewall rule for selected pods.
- podSelector — Which pods the policy applies to; {} means all in the namespace.
- policyTypes — Ingress, Egress, or both.
- Default deny — A policy selecting all pods with no allow rules.
- from / to — Allowed peers: podSelector, namespaceSelector, ipBlock.
- ports — Allowed ports and protocols.
- Additive — Policies only allow; the union of all allows applies.
- kubernetes.io/metadata.name — Automatic namespace label, handy for selectors.
- Calico — CNI that enforces NetworkPolicy, plus its own extended policies.
- Egress to DNS — Pods need UDP/TCP 53 to kube-dns or names stop resolving.
How: step by step
- Deploy web, api and db in namespace shop. Three tiers.
- Test all paths: everything open. test-matrix.sh.
- Apply default deny ingress and egress. Everything blocked, even DNS.
- Allow DNS egress to kube-system. Names resolve again.
- Allow web to api and api to db. Only the needed paths.
- Retest the matrix. Matches the design.
- Clean up. Delete the namespace; chapter done.
Example 1: Build the three tiers and a test matrix
Goal. Build the three tiers and a test matrix.
shop.sh
kubectl create ns shop
for t in web api db; do
kubectl create deploy $t -n shop --image=busybox:1.36 -- sh -c "mkdir -p /www && echo $t > /www/index.html && httpd -f -p 8080 -h /www"
kubectl expose deploy $t -n shop --port 8080
done
kubectl rollout status deploy -n shop web api db
test-matrix.sh
for src in web api db; do
for dst in web api db; do
[ $src = $dst ] && continue
code=$(kubectl exec -n shop deploy/$src -- wget -q -T 2 -O- http://$dst:8080 2>/dev/null || echo BLOCKED)
printf '%-4s -> %-4s %s\n' $src $dst "$code"
done
done
What each line does
Labels— create deploy adds app=web, app=api, app=db.busybox httpd— Each tier serves its name on port 8080, and busybox also has wget for testing.wget -T 2— Two-second timeout so blocked paths fail fast.BLOCKED— Printed when the call fails or times out.Expected now— Every path open.
Green output
$ bash shop.sh
$ bash test-matrix.sh
web -> api api
web -> db db
api -> web web
api -> db db
db -> web web
db -> api api
Expected result. All six paths work, including web to db, which a real design must forbid.
Example 2: Default deny, then fix DNS
Goal. Default deny, then fix DNS.
deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
allow-dns.yaml
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
What each line does
podSelector: {}— Every pod in shop.No rules— Both directions denied.DNS breaks— Egress deny also blocks port 53.namespaceSelector + podSelector— In one list item means AND: kube-dns pods in kube-system.UDP and TCP 53— DNS uses both.
Green output
$ kubectl apply -f deny-all.yaml
$ kubectl exec -n shop deploy/web -- wget -q -T 2 -O- http://api:8080
wget: bad address 'api:8080'
$ kubectl apply -f allow-dns.yaml
$ kubectl exec -n shop deploy/web -- wget -q -T 2 -O- http://api:8080
wget: download timed out
Expected result. First the name could not resolve (DNS blocked). After allow-dns the name resolves, but the connection still times out, because web to api is not allowed yet.
Example 3: Allow only web to api and api to db, then prove it
Goal. Allow only web to api and api to db, then prove it.
allow-app.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-from-web
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-from-api
namespace: shop
spec:
podSelector:
matchLabels:
app: db
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: api
ports:
- port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-web-api
namespace: shop
spec:
podSelector:
matchExpressions:
- {key: app, operator: In, values: [web, api]}
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchExpressions:
- {key: app, operator: In, values: [api, db]}
ports:
- port: 8080
What each line does
Ingress on api— Only from web, port 8080.Ingress on db— Only from api.Egress— web and api may send to api or db on 8080; combined with ingress rules only the allowed pairs work.Both sides— With default deny both ways, a path needs egress on the sender and ingress on the receiver.db— Has no egress apart from DNS.
Green output
$ kubectl apply -f allow-app.yaml
$ bash test-matrix.sh
web -> api api
web -> db BLOCKED
api -> web BLOCKED
api -> db db
db -> web BLOCKED
db -> api BLOCKED
Expected result. Only the two designed paths remain. web to db is blocked by db's ingress rule even though web's egress allows db.
Every command, step by step (kind lab with kubectl)
-
Calico running
kubectl get pods -n kube-system -l k8s-app=calico-nodeWhat it does: Enforcer present.
calico-node-4kx9z 1/1 Running -
Build shop
bash shop.shWhat it does: Three tiers.
deployment "db" successfully rolled out -
Open matrix
bash test-matrix.shWhat it does: All open.
web -> db db -
Deny all
kubectl apply -f deny-all.yamlWhat it does: Both directions.
networkpolicy.networking.k8s.io/default-deny created -
DNS broken
kubectl exec -n shop deploy/web -- nslookup apiWhat it does: Blocked.
;; connection timed out; no servers could be reached -
Allow DNS
kubectl apply -f allow-dns.yamlWhat it does: Port 53 to kube-dns.
networkpolicy.networking.k8s.io/allow-dns created -
Allow app paths
kubectl apply -f allow-app.yamlWhat it does: Three policies.
networkpolicy.networking.k8s.io/api-from-web created -
List
kubectl get netpol -n shopWhat it does: Five policies.
NAME POD-SELECTOR allow-dns <none> api-from-web app=api db-from-api app=db default-deny <none> egress-web-api app in (api,web) -
Final matrix
bash test-matrix.shWhat it does: Two paths.
web -> api api api -> db db -
Describe
kubectl describe netpol db-from-api -n shopWhat it does: Readable rules.
Allowing ingress traffic: To Port: 8080/TCP From: PodSelector: app=api -
Clean up
kubectl delete ns shop kind delete cluster --name ch62What it does: Chapter done.
namespace "shop" deleted
When it is red 1
Ingress controller cannot reach the app after default deny
Cause. No ingress allow from the ingress-nginx namespace.
Fix, step by step
- Allow from namespaceSelector kubernetes.io/metadata.name: ingress-nginx on the app port.
When it is red 2
Policies applied but nothing is blocked
Cause. The CNI does not enforce NetworkPolicy (for example kindnet or Flannel).
Fix, step by step
- Use Calico, Cilium, or the VPC CNI with network policy enabled.
When it is red 3
wget: bad address after default deny
Cause. Egress to DNS is blocked.
Fix, step by step
- Add the allow-dns policy for UDP and TCP 53.
When it is red 4
Allowed traffic still times out
Cause. Only one side allowed: missing egress on the sender or ingress on the receiver.
Fix, step by step
- Check both policies for the path.
When it is red 5
Policy allows a whole namespace by accident
Cause. namespaceSelector and podSelector were separate list items (OR) instead of one item (AND).
Fix, step by step
- Put both selectors in the same - item.
Practice tasks
Easy Task 1: Tiers
Why: Build.
What to do: Create shop.
- shop.sh.
Solution
kubectl get pods -n shopEasy Task 2: Matrix
Why: Test.
What to do: Run the open matrix.
- test-matrix.sh.
Solution
bash test-matrix.shEasy Task 3: Deny
Why: Apply.
What to do: Apply default deny.
- podSelector {}.
Solution
kubectl get netpol -n shopMedium Task 4: DNS
Why: Fix.
What to do: Allow DNS.
- 53 both protocols.
Solution
kubectl exec -n shop deploy/web -- nslookup apiMedium Task 5: Paths
Why: Allow.
What to do: Allow web-api-db.
- allow-app.yaml.
Solution
bash test-matrix.shMedium Task 6: Describe
Why: Read.
What to do: Describe each policy.
- describe netpol.
Solution
kubectl describe netpol -n shopMedium Task 7: OR bug
Why: Break.
What to do: Split the selectors and see the effect.
- Two items.
Solution
kubectl get netpol allow-dns -n shop -o yamlMedium Task 8: ipBlock
Why: Egress.
What to do: Allow an outside CIDR.
- ipBlock.
Solution
kubectl explain networkpolicy.spec.egress.to.ipBlockHard Task 9: CRED
Why: Main task.
What to do: Build the payments policies.
- Five rules.
Solution
kubectl get netpol -n paymentsHard Task 10: Audit
Why: Prove.
What to do: Write the auditor matrix.
- All pairs.
Solution
bash test-matrix.shHomework: try at home
For CRED, write policies for a payments namespace: default deny both ways, DNS allowed, gateway to payments-api on 8443, payments-api to ledger-db on 5432, and payments-api egress to an outside bank range 198.51.100.0/24 on 443 only. Prove it with a test matrix.
Recap
- NetworkPolicy is a pod firewall enforced by the CNI.
- Selected pods deny everything not allowed; unselected pods stay open.
- Start with default deny, then allow DNS, then each needed path.
- A path needs egress on the sender and ingress on the receiver.
- Chapter 62 done. Next chapter: Ingress and Gateway API.
FAQ
Q. Should every namespace get default deny?
In production, yes for application namespaces, added together with DNS and monitoring allows. Roll it out per namespace with a test matrix so nothing breaks silently.
Q. How do I see what Calico is dropping?
Calico's own policies can log denied packets, and tools like calicoctl or flow logs in Calico Cloud show them. With standard policies, test paths with a matrix script.
Q. Do NetworkPolicies apply to hostNetwork pods?
Generally no. Pods using the node's network are treated as the node, so rely on node firewalls for them.
Q. Does NetworkPolicy work on kind by default?
No, kindnet does not enforce it; that is why we used Calico.
Q. Can I block traffic to the internet?
Yes, egress default deny with only specific ipBlock allows.
Q. Can I write deny rules?
Not in the standard API; Calico and Cilium have their own policies with deny and order.
Q. Do policies affect existing connections?
Usually yes after enforcement; test during a quiet window.
Q. Does it filter by DNS name?
Not in the standard API; Calico and Cilium offer DNS-based rules.
Q. How do I allow monitoring?
Allow ingress from the monitoring namespace to the metrics port.
Interview questions
-
What is a NetworkPolicy?
Model answer: A namespaced rule set that allows ingress or egress for selected pods; the CNI enforces it.
-
What does default deny look like?
Model answer: podSelector {} with policyTypes Ingress and Egress and no rules.
-
Why does DNS break after default deny?
Model answer: Egress to kube-dns on port 53 is blocked; allow it explicitly.
-
AND vs OR in from rules?
Model answer: Selectors in one item are ANDed; separate items are ORed.
-
Which CNIs enforce NetworkPolicy?
Model answer: Calico, Cilium, and the AWS VPC CNI with network policy enabled; not plain Flannel or kindnet.
-
How would you prove to an auditor that only approved paths exist?
Model answer: Apply default deny, add explicit allows, run an automated test matrix of every source and destination pair in CI, and keep the policies in Git with review.
Quick quiz
Q1. What happens to unselected pods?
Fully open.
Q2. Are there deny rules?
No, only allows.
Q3. What breaks first with egress deny?
DNS.
Q4. Is enforcement built into Kubernetes?
No, the CNI enforces it.
Q5. AND vs OR in selectors?
Same item AND, separate items OR.
Samajla ka?
Lab
- Create a fresh kind cluster named ch62 with the default CNI turned off (lesson 62.1 installs Calico).
- Keep YAML files in a folder named k8s-ch62.
- After every Service you create, run kubectl get svc,endpointslices.
- Test from inside the cluster with a curl pod before testing from your laptop.
- Delete the cluster at the end with kind delete cluster --name ch62.
Practice task
Run one shop app with frontend, api and database, expose each one with the right Service type, and block every path the app does not need with NetworkPolicy.
Ravindra Bagale's Tip
Interview madhe 'ClusterIP, NodePort aani LoadBalancer madhla farak' aani 'Service pods kase shodhto' he prashna nakki yetat. Lab karun uttar dya.
In short
- A Service gives pods a stable name, IP and load balancing.
- ClusterIP is internal, NodePort opens a port on every node, LoadBalancer adds an external IP.
- CoreDNS turns service names into IPs; kube-proxy turns service IPs into pod IPs.
- NetworkPolicy needs a CNI that enforces it, like Calico or Cilium.