Ravindra BagaleCourses & study guides मराठी

Chapter 62: Kubernetes Services, Networking and Load Balancing: CNI, ClusterIP, NodePort, LoadBalancer, DNS and NetworkPolicy

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.
Deploy web, api and db in namespace shop. Test all paths: everything open. Apply default deny ingress and egress. Allow DNS egress to kube-system. Allow web to api and api to db. Retest the matrix. Clean up.
NetworkPolicy: default deny, DNS, and only the paths you need

How: step by step

  1. Deploy web, api and db in namespace shop. Three tiers.
  2. Test all paths: everything open. test-matrix.sh.
  3. Apply default deny ingress and egress. Everything blocked, even DNS.
  4. Allow DNS egress to kube-system. Names resolve again.
  5. Allow web to api and api to db. Only the needed paths.
  6. Retest the matrix. Matches the design.
  7. 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)

  1. Calico running

    kubectl get pods -n kube-system -l k8s-app=calico-node
    

    What it does: Enforcer present.

    calico-node-4kx9z   1/1   Running
    
  2. Build shop

    bash shop.sh
    

    What it does: Three tiers.

    deployment "db" successfully rolled out
    
  3. Open matrix

    bash test-matrix.sh
    

    What it does: All open.

    web  -> db   db
    
  4. Deny all

    kubectl apply -f deny-all.yaml
    

    What it does: Both directions.

    networkpolicy.networking.k8s.io/default-deny created
    
  5. DNS broken

    kubectl exec -n shop deploy/web -- nslookup api
    

    What it does: Blocked.

    ;; connection timed out; no servers could be reached
    
  6. Allow DNS

    kubectl apply -f allow-dns.yaml
    

    What it does: Port 53 to kube-dns.

    networkpolicy.networking.k8s.io/allow-dns created
    
  7. Allow app paths

    kubectl apply -f allow-app.yaml
    

    What it does: Three policies.

    networkpolicy.networking.k8s.io/api-from-web created
    
  8. List

    kubectl get netpol -n shop
    

    What 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)
    
  9. Final matrix

    bash test-matrix.sh
    

    What it does: Two paths.

    web  -> api  api
    api  -> db   db
    
  10. Describe

    kubectl describe netpol db-from-api -n shop
    

    What it does: Readable rules.

    Allowing ingress traffic:
        To Port: 8080/TCP
        From:
          PodSelector: app=api
    
  11. Clean up

    kubectl delete ns shop
    kind delete cluster --name ch62
    

    What 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

  1. 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

  1. 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

  1. 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

  1. 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

  1. Put both selectors in the same - item.

Practice tasks

Easy Task 1: Tiers

Why: Build.

What to do: Create shop.

How:
  1. shop.sh.
Solution
kubectl get pods -n shop

Easy Task 2: Matrix

Why: Test.

What to do: Run the open matrix.

How:
  1. test-matrix.sh.
Solution
bash test-matrix.sh

Easy Task 3: Deny

Why: Apply.

What to do: Apply default deny.

How:
  1. podSelector {}.
Solution
kubectl get netpol -n shop

Medium Task 4: DNS

Why: Fix.

What to do: Allow DNS.

How:
  1. 53 both protocols.
Solution
kubectl exec -n shop deploy/web -- nslookup api

Medium Task 5: Paths

Why: Allow.

What to do: Allow web-api-db.

How:
  1. allow-app.yaml.
Solution
bash test-matrix.sh

Medium Task 6: Describe

Why: Read.

What to do: Describe each policy.

How:
  1. describe netpol.
Solution
kubectl describe netpol -n shop

Medium Task 7: OR bug

Why: Break.

What to do: Split the selectors and see the effect.

How:
  1. Two items.
Solution
kubectl get netpol allow-dns -n shop -o yaml

Medium Task 8: ipBlock

Why: Egress.

What to do: Allow an outside CIDR.

How:
  1. ipBlock.
Solution
kubectl explain networkpolicy.spec.egress.to.ipBlock

Hard Task 9: CRED

Why: Main task.

What to do: Build the payments policies.

How:
  1. Five rules.
Solution
kubectl get netpol -n payments

Hard Task 10: Audit

Why: Prove.

What to do: Write the auditor matrix.

How:
  1. All pairs.
Solution
bash test-matrix.sh

Homework: 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

  1. What is a NetworkPolicy?

    Model answer: A namespaced rule set that allows ingress or egress for selected pods; the CNI enforces it.

  2. What does default deny look like?

    Model answer: podSelector {} with policyTypes Ingress and Egress and no rules.

  3. Why does DNS break after default deny?

    Model answer: Egress to kube-dns on port 53 is blocked; allow it explicitly.

  4. AND vs OR in from rules?

    Model answer: Selectors in one item are ANDed; separate items are ORed.

  5. Which CNIs enforce NetworkPolicy?

    Model answer: Calico, Cilium, and the AWS VPC CNI with network policy enabled; not plain Flannel or kindnet.

  6. 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

  1. Create a fresh kind cluster named ch62 with the default CNI turned off (lesson 62.1 installs Calico).
  2. Keep YAML files in a folder named k8s-ch62.
  3. After every Service you create, run kubectl get svc,endpointslices.
  4. Test from inside the cluster with a curl pod before testing from your laptop.
  5. 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.