Field note

Learning the network by denying it first

Applying default-deny NetworkPolicies per use case to segment Kubernetes traffic and turn application data flows into explicit rules.

· Zero Trust · Kubernetes · NetworkPolicy · Calico · Cilium · GitOps

Segmentation as a learning method

Kubernetes makes it easy for workloads to communicate. That convenience also means a compromised workload may reach more services than its use case requires. I wanted my homelab namespaces to start from the opposite assumption: traffic is denied until a workload has an understood reason to send or receive it.

This turns Zero Trust into an engineering practice. Each use case gets its own explicit network boundaries, and implementing those boundaries forces me to learn the real data flow rather than relying on an architecture diagram alone.

Start with default deny

The cluster uses a Calico GlobalNetworkPolicy named zt-default-deny. At order 1000 it denies ingress and egress for application namespaces, while system namespaces such as kube-system, calico-system, longhorn-system and metallb-system remain outside its scope.

Individual Kubernetes NetworkPolicy resources then allow the traffic needed by each workload. These policies live next to their component manifests and are applied with Kustomize through Flux. That keeps the application, its dependencies and its network permissions together in Git.

LayerResponsibility
Global Calico policyEstablish default-deny ingress and egress
Namespace policyPermit only the flows required by that use case
Component repositoryKeep policy beside the workload it protects
Flux and KustomizeApply the declared state consistently

Build policy from observed behavior

With the deny policy active, an undocumented dependency becomes visible because the workload can no longer reach it. I can then investigate the failed flow and decide whether it is required before creating an allow rule.

For each use case I work through the same questions:

  1. Which workloads may initiate traffic?
  2. Which in-cluster services must they reach?
  3. Which ports and protocols does that communication use?
  4. Does the workload need DNS resolution or an external destination?
  5. Which workloads are allowed to connect to it?

The result is more than isolation between namespaces. I gain a practical inventory of data flows, and every exception becomes a reviewable statement of intended behavior.

The egress limitation I see today

The difficult boundary is internet access. In my current Calico setup, policies cannot express an external dependency by fully qualified domain name. Kubernetes NetworkPolicy works with selectors, ports and IP ranges; an application description such as “allow this vendor API” does not translate cleanly when the provider’s addresses can change.

If a namespace needs general internet access, I currently have to allow a much broader egress range than the use case actually requires. The workload is still segmented from other internal workloads, but the internet boundary is less precise than I want.

That gap matters because a policy should describe business intent as closely as possible. Allowing api.example.com is understandable. Allowing every external IP because that name may resolve dynamically creates a larger path than the application needs.

Cilium on the roadmap

FQDN-aware egress policy is one of the capabilities I want to explore with Cilium. It would let an external dependency be expressed by DNS name and allow the policy engine to follow the corresponding addresses.

Moving CNI is a wider platform decision, so this remains on the roadmap. The current Calico implementation is still valuable: it provides default-deny behavior, segments workloads per use case and exposes the actual communication patterns. The limitation is now explicit and gives the Cilium evaluation a concrete requirement.

What this practice changes

Network policy has become part of how I deploy a workload, rather than a control added after the application works. A new use case is complete only when its required ingress and egress paths are understood and declared alongside it.

This is the most useful part of the experiment. Zero Trust becomes visible in the repository: deny by default, grant the minimum path, observe the result and refine the rule when the evidence changes.

Implementation reference

The implementation is documented in the Network Policies homelab page. It uses Calico GlobalNetworkPolicy, Kubernetes NetworkPolicy, Kustomize and Flux.