This episode covers zero trust security with Calico: building default-deny per namespace and per pod, label-based microsegmentation, modeling workload endpoints, and the principles of zero trust in Kubernetes networking.

Zero trust is a simple principle with an implementation that demands discipline: don't trust anything by default, verify every connection. In the Kubernetes networking context, that means no workload can talk to another workload unless explicitly allowed.
In episode 13 we translate that principle into Calico practice: default-deny at the namespace and pod level, microsegmentation with labels, and modeling workload endpoints as the security unit. You'll leave this episode with a pattern you can use directly as a cluster baseline.
In a cluster without policy, every pod can reach every other pod — there are no boundaries. Zero trust places boundaries at several points: namespaces (boundary between teams), labels (boundary between applications), and ports (boundary between functions). All three layers can be enabled together with Calico.
The first step of zero trust is ensuring that without explicit policy, there is no traffic. In episode 6 we created a global deny with GlobalNetworkPolicy. In this episode we apply it more granularly: default-deny per namespace.
To close an entire namespace at once:
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: default-deny
namespace: payments
spec:
selector: all()
types:
- Ingress
- Egress
ingress: []
egress: []This policy matches every pod in the payments namespace, then with no rules at all — the result is all inbound and outbound traffic is denied. Teams that want to open access add an Allow policy in the appropriate tier.
Applying default deny to the whole cluster at once is scary. Here's the recommended gradual activation:
kubectl label ns payments policy.open=true
calicoctl apply -f - <<EOF
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: default-deny
namespace: payments
spec:
selector: all()
types:
- Ingress
- Egress
ingress: []
egress: []
EOF
calicoctl get networkpolicy -n paymentsStart namespace by namespace, watch the alarms, then scale to the whole cluster.
Microsegmentation means breaking the network into small label-based segments. Here's a security model example for a payments application:
tier: web can only receive from the ingress controller.tier: api only receives from tier: web and exits to tier: db.tier: db only receives from tier: api on the database port.apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: api-allow-web
namespace: payments
spec:
selector: tier == 'api'
types:
- Ingress
ingress:
- action: Allow
protocol: TCP
source:
selector: tier == 'web'
destination:
ports:
- 8080This policy only allows tier: web to reach tier: api on port 8080. Other traffic is denied by the namespace default deny.
All of this works because every pod is represented as a workload endpoint with its pod's labels. When a pod gets a new label, the policies using that label apply automatically — no restart and no manual edits:
calicoctl get workloadendpoints -n payments -o wide
kubectl label pod api-pod-xyz tier=api
calicoctl get workloadendpoints -n payments -o wide | grep api-podcalicoctl get workloadendpoints -o wide shows the endpoints with their labels and nodes. Label changes are immediately reflected in the endpoints.
Before writing policy, map the legitimate connection flows. For each workload pair, write: source, destination, port, and protocol. From this inventory, each row becomes one Allow rule. The resulting policy is concise and auditable:
ingress-nginx -> tier:web (TCP 80, 443)
tier:web -> tier:api (TCP 8080)
tier:api -> tier:db (TCP 5432)
tier:api -> kube-dns (UDP 53)
tier:api -> api.external.example.com (TCP 443)From the inventory above, each row becomes a NetworkPolicy or a rule within a larger policy. Don't forget the DNS row — without it, domain egress (episode 11) and ordinary resolution will fail.
After the policies are applied, test with a pod that should be denied:
kubectl run intruder --image=busybox -n payments --command -- sleep 3600
kubectl exec -n payments intruder -- wget -T 3 -q -O - http://api:8080
kubectl exec -n payments deploy/api -- wget -T 3 -q -O - http://db:5432Both commands should time out. If they succeed, there's an Allow policy that's too broad — audit the rules and narrow their selectors.
To see the decisions the dataplane made during testing:
kubectl logs -n calico-system ds/calico-node | grep -iE "deny|drop" | tail -20Episode 13 turns the zero trust principle into actionable policy: default-deny per namespace and per pod, label-based microsegmentation, and modeling workload endpoints as the security unit.
Key takeaways:
ingress: [] and egress: [] closes a namespace.tier as segments.In the next episode, episode 14, we cover host endpoints and node security — protecting the Kubernetes nodes themselves with HostEndpoint, locking down management ports, and applying policy to node-bound and inter-node traffic.