Learn Calico - Basic Network Policy (NetworkPolicy)
Episode 5 of 23

Learn Calico - Basic Network Policy (NetworkPolicy)

This episode dissects Calico's namespaced NetworkPolicy: selector structure, ingress and egress, ports and protocols, the default deny principle, and the order of policy evaluation using tiers.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

This is the episode that most often gets people to adopt Calico: network policy. In this episode you learn to write policies that control traffic in and out of workloads based on labels, ports, and protocols — and more importantly, you understand when a packet is allowed or denied.

The ground rules must be clear from the start: in Calico, the default is allow. If no policy matches a workload, all traffic flows freely. To build real security, you must close that gap with a default deny policy — a theme we dig deeper into in episode 13.

Namespaced NetworkPolicy

Differences from Kubernetes NetworkPolicy

Calico provides a NetworkPolicy with the projectcalico.org/v3 apiVersion. Syntactically it's similar to networking.k8s.io/v1, but with advantages: selectors use Calico labels, explicit actions (Allow/Deny/Pass/Log), and broader protocol support such as SCTP.

Basic Calico NetworkPolicy
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: allow-web-from-app
  namespace: default
spec:
  selector: app == 'web'
  types:
    - Ingress
  ingress:
    - action: Allow
      protocol: TCP
      source:
        selector: app == 'app'
      destination:
        ports:
          - 8080

This policy allows pods labeled app: web to receive TCP from pods labeled app: app on port 8080. Notice action: Allow — in Calico the action is explicit, not just adding a rule.

Selectors and Namespace Selectors

Selectors can reach across namespaces with namespaceSelector:

Cross-namespace selector
      source:
        namespaceSelector: project == 'frontend'
        selector: app == 'api-client'

The combination of namespaceSelector and selector means: pods from namespaces matching the label project: frontend and labeled app: api-client.

The Default Deny Principle

Why You Must Close the Default Allow

As long as no policy exists, kubectl exec and cross-namespace access run freely. To change that, create a policy that matches the workload and has no Allow rules:

Default deny per workload
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: default-deny-web
  namespace: default
spec:
  selector: app == 'web'
  types:
    - Ingress
    - Egress
  ingress: []
  egress: []

A policy with ingress: [] and egress: [] means: all traffic not matched by another rule will be denied for this workload. This is the basic microsegmentation pattern.

Evaluation Order

When a pod is matched by several policies, the rules from all policies are combined and evaluated in order by tiers (episode 6). The first matching rule determines the action. That's why rule order matters: put a more specific deny before a broad allow.

Verify the policies applied to a specific workload:

Check the policies on a workload
calicoctl get networkpolicy -n default -o yaml
calicoctl get workloadendpoints -n default
kubectl describe networkpolicy -n default

kubectl describe networkpolicy shows a summary of the rules attached to a specific pod — useful when a policy seems to have no effect.

Ingress, Egress, and Combinations

Controlling Egress

Besides ingress, you can restrict outbound traffic. For example, a client pod may only exit to port 443:

Restrict client egress
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: egress-client
  namespace: default
spec:
  selector: app == 'client'
  types:
    - Egress
  egress:
    - action: Allow
      protocol: TCP
      destination:
        selector: app == 'api'
        ports:
          - 443

Remember: egress also needs DNS permission. If a workload must resolve names, add a rule for CoreDNS (we cover this in detail in episode 11).

Protocols and Ports

Rule protocols support TCP, UDP, SCTP, and ICMP. For port ranges:

Port ranges and protocols
      destination:
        ports:
          - 9000
          - 10000:11000

10000:11000 means ports 10000 through 11000. protocol and ports must always be paired together.

Testing Policy in the Field

After applying the policy, test it with pods on both sides:

Test the policy's effect
kubectl run allow-src --image=busybox --labels app=app --command -- sleep 3600
kubectl run deny-src --image=busybox --labels app=other --command -- sleep 3600
kubectl exec allow-src -- wget -q -O - http://web.default.svc.cluster.local:8080
kubectl exec deny-src -- wget -q -O - http://web.default.svc.cluster.local:8080

The allow-src pod should succeed, while deny-src will time out because it isn't matched by an Allow policy. If deny-src actually succeeds, there's an allow policy that's too broad — or the policy hasn't been applied.

Conclusion

Episode 5 equips you with Calico's core security skills: writing namespaced NetworkPolicies, understanding the default deny principle, controlling ingress and egress, and testing the real effect of policies.

Key takeaways:

  • Calico's default is allow; default deny must be built with an empty policy.
  • Policies are combined and evaluated in order; rule order determines the result.
  • action is explicit: Allow, Deny, Pass, and Log.
  • Selectors can be combined with namespaceSelector for cross-namespace matching.
  • Protocol and ports always pair together; port ranges use the start:end format.
  • kubectl describe networkpolicy and pod tests are the fastest way to validate.

In the next episode, episode 6, we go up a level: GlobalNetworkPolicy and tiers — cluster-scoped policy that applies to all nodes, GlobalNetworkSet, and the security, platform, and application tier hierarchy that governs the order of policy evaluation across the whole cluster.