Restrict Django Pod Traffic With Kubernetes NetworkPolicies
Lock down network access for Django on Kubernetes: default-deny ingress and egress, allow the ingress controller to reach web pods, let web and Celery reach PostgreSQL, Redis, and DNS, permit required external APIs, and test policies without breaking the app.
Problem statement
By default, every pod in a Kubernetes cluster can talk to every other pod and to the internet. If an attacker gets code execution in your Django pod through a vulnerable dependency, they can reach the database directly, scan other namespaces, and send data anywhere. NetworkPolicies let you declare which traffic each pod may send and receive, so a compromised pod can reach only what Django legitimately needs.
- Confirm your CNI enforces NetworkPolicies (Calico, Cilium, and most managed clusters' policy options do).
- Apply default-deny ingress and egress policies to the app namespace.
- Allow ingress to web pods only from the ingress controller or Gateway namespace, on the app port.
- Allow egress from web and Celery pods to PostgreSQL, Redis, and DNS (port 53 to kube-dns).
- Allow egress to required external services (email API, payment provider, Sentry) by CIDR or with FQDN policies if your CNI supports them.
Step-by-step solution
1. Default deny
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: shop
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
With this alone, nothing in the namespace can send or receive traffic, including DNS. Apply it together with the allow rules below, or in a staging namespace first.
2. Allow DNS
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 }
Forgetting DNS is the most common reason a new policy set breaks everything with "Temporary failure in name resolution".
3. Allow ingress from the gateway
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: web-ingress, namespace: shop }
spec:
podSelector: { matchLabels: { app: web } }
policyTypes: ["Ingress"]
ingress:
- from:
- namespaceSelector: { matchLabels: { kubernetes.io/metadata.name: gateway } }
ports: [{ protocol: TCP, port: 8000 }]
Kubelet health probes come from the node and are generally not subject to NetworkPolicies in most CNIs, but verify in your cluster.
4. Allow egress to data stores
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: app-egress-data, namespace: shop }
spec:
podSelector: { matchExpressions: [{ key: app, operator: In, values: [web, celery] }] }
policyTypes: ["Egress"]
egress:
- to: [{ ipBlock: { cidr: 10.20.0.0/24 } }] # managed PostgreSQL subnet
ports: [{ protocol: TCP, port: 5432 }]
- to: [{ podSelector: { matchLabels: { app: redis } } }]
ports: [{ protocol: TCP, port: 6379 }]
5. External APIs and testing
Standard NetworkPolicies only match IPs and CIDRs, which is awkward for SaaS APIs behind changing addresses. Options: route outbound traffic through an egress proxy and allow only the proxy, or use CNI features like Cilium's toFQDNs. Test in staging with each policy added in turn: run the test suite, trigger emails and payments, and watch for timeouts. Tools such as kubectl exec ... -- python -c "import socket; socket.create_connection(('db', 5432), 3)" check specific paths quickly.
Explanation
NetworkPolicies are additive allow lists evaluated by the cluster's CNI plugin. Once any policy selects a pod for a direction (ingress or egress), only traffic explicitly allowed by some policy is permitted in that direction. A default-deny policy that selects all pods turns the namespace into a closed system, and specific policies open the paths Django needs. This limits lateral movement after a compromise and documents dependencies as code.
Edge cases / notes
- CNI support. Some basic network plugins ignore NetworkPolicies entirely; policies then appear to apply but do nothing.
- Migrations and jobs. Job pods need the same egress as web pods; label them so the policies select them.
- Metrics and logs. Allow ingress from Prometheus for metrics endpoints and egress to log collectors if they are network-based.
Next steps
- Review cluster hardening with the Django Kubernetes Security Checklist.
- Route external traffic with Expose Django on Kubernetes With Gateway API or Ingress.
- Cover operations with the Django Kubernetes Production Checklist.
FAQ
Are NetworkPolicies enabled by default?
Pods are open by default. Policies only take effect when your CNI supports them and you create them.
Why did everything break after adding default deny?
DNS egress was not allowed. Add a policy allowing port 53 to kube-dns.
How do I allow access to external APIs?
By CIDR with standard policies, through an egress proxy, or with FQDN rules if your CNI supports them.
Do health probes need a NetworkPolicy rule?
Usually not, since kubelet probes come from the node, but confirm with your CNI.