Calico

Calico network policy extends Kubernetes network policy by adding features such as namespace selectors and traffic logging.

The examples below demonstrate the usage of Calico network policies.

How to allow / deny traffic from other namespace ?

Let's verify the network connectivity between podinfo and podtato-head pods by testing traffic flow in both directions. First, let's check if podinfo can reach podtato-head:

kubectl exec -it deployments/podinfo -c netshoot -- /bin/zsh
curl podtato-head-entry.podtato:9000

The output shows:

<html>
  <head>
    <title>Hello Podtato!</title>
    <link rel="stylesheet" href="./assets/css/styles.css"/>
    <link rel="stylesheet" href="./assets/css/custom.css"/>
  </head>
  <body style="background-color: #849abd;color: #faebd7;">
    <main class="container">
      <div class="text-center">
        <h1>Hello from <i>pod</i>tato head!</h1>
        <div style="width:700px; height:800px; margin:auto; position:relative;">
          <img src="./assets/images/body/body.svg" style="position:absolute;margin-top:80px;margin-left:200px;">
          <img src="./parts/hat/hat.svg" style="position:absolute;margin-left:200px;margin-top:0px;">
          <img src="./parts/left-arm/left-arm.svg" style="position:absolute;top:100px;left:-50px;">
          <img src="./parts/right-arm/right-arm.svg" style="position:absolute;top:100px;left:450px;">
          <img src="./parts/left-leg/left-leg.svg" style="position:absolute;top:480px;left: -0px;" >
          <img src="./parts/right-leg/right-leg.svg" style="position:absolute;top:480px;left: 400px;">
        </div>
        <h2> Version v0.1.0 </h2>
      </div>
    </main>
  </body>
</html>

Then let's verify the traffic flow in the opposite direction, from podtato-head to podinfo:

kubectl -n podtato exec -it deployments/podtato-head-entry -c netshoot -- /bin/zsh
curl podinfo.default:9898

The output shows:

{
  "hostname": "podinfo-7f9d98d56d-c8zhf",
  "version": "6.7.1",
  "revision": "6b7aab8a10d6ee8b895b0a5048f4ab0966ed29ff",
  "color": "#34577c",
  "logo": "https://raw.githubusercontent.com/stefanprodan/podinfo/gh-pages/cuddle_clap.gif",
  "message": "greetings from podinfo v6.7.1",
  "goos": "linux",
  "goarch": "arm64",
  "runtime": "go1.23.2",
  "num_goroutine": "8",
  "num_cpu": "8"
}#

Now it's time to create a Calico NetworkPolicy that allows ingress traffic from the podinfo pod in the default namespace while blocking all egress traffic from podtato-head:

cat <<EOF | kubectl apply -f -
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: allow-from-default-ns
  namespace: podtato
spec:
  selector: component == 'podtato-head-entry'
  types:
    - Ingress
    - Egress
  ingress:
    - action: Allow
      protocol: TCP
      source:
        selector: app == 'podinfo'
        namespaceSelector: kubernetes.io/metadata.name == 'default'
      destination:
        ports:
          - 9000
  egress: []
EOF

Now let's verify the created NetworkPolicy:

kubectl get networkpolicies.projectcalico.org -A

Possible output can be as below:

NAMESPACE   NAME                            CREATED AT
podtato     default.allow-from-default-ns   2025-03-15T21:50:07Z

Let's verify that traffic is allowed from podinfo to podtato-head by sending a request from the podinfo pod:

kubectl exec -it deployments/podinfo -c netshoot -- /bin/zsh
curl podtato-head-entry.podtato:9000

The output shows:

<html>
  <head>
    <title>Hello Podtato!</title>
    <link rel="stylesheet" href="./assets/css/styles.css"/>
    <link rel="stylesheet" href="./assets/css/custom.css"/>
  </head>
  <body style="background-color: #849abd;color: #faebd7;">
    <main class="container">
      <div class="text-center">
        <h1>Hello from <i>pod</i>tato head!</h1>
        <div style="width:700px; height:800px; margin:auto; position:relative;">
          <img src="./assets/images/body/body.svg" style="position:absolute;margin-top:80px;margin-left:200px;">
          <img src="./parts/hat/hat.svg" style="position:absolute;margin-left:200px;margin-top:0px;">
          <img src="./parts/left-arm/left-arm.svg" style="position:absolute;top:100px;left:-50px;">
          <img src="./parts/right-arm/right-arm.svg" style="position:absolute;top:100px;left:450px;">
          <img src="./parts/left-leg/left-leg.svg" style="position:absolute;top:480px;left: -0px;" >
          <img src="./parts/right-leg/right-leg.svg" style="position:absolute;top:480px;left: 400px;">
        </div>
        <h2> Version v0.1.0 </h2>
      </div>
    </main>
  </body>
</html>#

Next let's verify that traffic is blocked from podtato-head to podinfo by attempting to send a request from the podtato-head pod:

kubectl -n podtato exec -it deployments/podtato-head-entry -c netshoot -- /bin/zsh
curl podinfo.default:9898 --connect-timeout 10

The output shows that the connection timed out, indicating that the network policy is successfully blocking traffic from the podtato-head pod to podinfo:

curl: (28) Resolving timed out after 10003 milliseconds

How to allow / deny traffic from specific IP range ?

Let's get the IP addresses of our pods to use in the network policy. First, let's get the podtato-head pod IP:

kubectl get pods -n podtato -o json | jq -r '.items[] | select(.metadata.name | startswith("podtato-head-entry")) | .status.podIP'
192.168.41.196
kubectl get pods -n default -o json | jq -r '.items[] | select(.metadata.name | startswith("podinfo")) | .status.podIP'
192.168.41.194
192.168.209.137

As a next step let's create a Calico NetworkPolicy that allows traffic from specific podinfo pod IPs to the podtato-head pod:

kubectl -n podtato delete networkpolicies.projectcalico.org default.allow-from-default-ns

cat <<EOF | kubectl apply -f -
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: allow-from-default-pod-ip
  namespace: podtato
spec:
  selector: component == 'podtato-head-entry'
  types:
    - Ingress
    - Egress
  ingress:
    - action: Allow
      protocol: TCP
      source:
        nets:
        - 192.168.41.194/32
        - 192.168.209.137/32
  egress: []
EOF

After that verify the network policies that are currently active in our cluster:

kubectl get networkpolicies.projectcalico.org -A

Output:

NAMESPACE   NAME                                CREATED AT
podtato     default.allow-from-default-pod-ip   2025-03-15T22:04:17Z

Verify that traffic is allowed to flow from the podinfo pods to the podtato-head service by executing a curl command from one of the podinfo pods:

kubectl exec -it deployments/podinfo -c netshoot -- /bin/zsh
curl podtato-head-entry.podtato:9000

The output shows:

<html>
  <head>
    <title>Hello Podtato!</title>
    <link rel="stylesheet" href="./assets/css/styles.css"/>
    <link rel="stylesheet" href="./assets/css/custom.css"/>
  </head>
  <body style="background-color: #849abd;color: #faebd7;">
    <main class="container">
      <div class="text-center">
        <h1>Hello from <i>pod</i>tato head!</h1>
        <div style="width:700px; height:800px; margin:auto; position:relative;">
          <img src="./assets/images/body/body.svg" style="position:absolute;margin-top:80px;margin-left:200px;">
          <img src="./parts/hat/hat.svg" style="position:absolute;margin-left:200px;margin-top:0px;">
          <img src="./parts/left-arm/left-arm.svg" style="position:absolute;top:100px;left:-50px;">
          <img src="./parts/right-arm/right-arm.svg" style="position:absolute;top:100px;left:450px;">
          <img src="./parts/left-leg/left-leg.svg" style="position:absolute;top:480px;left: -0px;" >
          <img src="./parts/right-leg/right-leg.svg" style="position:absolute;top:480px;left: 400px;">
        </div>
        <h2> Version v0.1.0 </h2>
      </div>
    </main>
  </body>
</html>#

Now let's verify traffic flow in the opposite direction - from podtato-head to podinfo:

kubectl -n podtato exec -it deployments/podtato-head-entry -c netshoot -- /bin/zsh
curl podinfo.default:9898 --connect-timeout 10

The output shows that the connection timed out:

curl: (28) Resolving timed out after 10002 milliseconds

How to generate logs for specific traffic ?

Let's define a network policy that logs and denies traffic from podinfo to podtato-head:

kubectl -n podtato delete networkpolicies.projectcalico.org default.allow-from-default-pod-ip

cat <<EOF | kubectl apply -f -
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
  name: log-and-deny-ingress
  namespace: podtato
spec:
  selector: component == 'podtato-head-entry'
  types:
    - Ingress
    - Egress
  ingress:
    - action: Log
      protocol: TCP
      source:
        selector: app == 'podinfo'
  egress: []
EOF

Generate traffic from podinfo to podtato-head to trigger the logging policy

kubectl exec -it deployments/podinfo -c netshoot -- /bin/zsh
curl podtato-head-entry.podtato:9000

Check the logs from the Calico node pod to see the logged traffic:

kubectl logs -n calico-system calico-node-kh2jg | grep log-and-deny-ingress

The output shows the following:

Defaulted container "calico-node" out of: calico-node, flexvol-driver (init), install-cni (init)
2025-03-15 22:10:52.248 [INFO][89] felix/label_inheritance_index.go 185: Updating selector id=Policy(tier=default, name=podtato/default.log-and-deny-ingress) selector=(component == "podtato-head-entry" && projectcalico.org/namespace == "podtato")
2025-03-15 22:10:52.250 [INFO][89] felix/int_dataplane.go 2041: Received *proto.ActivePolicyUpdate update from calculation graph msg=id:<tier:"default" name:"podtato/default.log-and-deny-ingress" > policy:<namespace:"podtato" inbound_rules:<action:"log" protocol:<name:"tcp" > src_ip_set_ids:"s:pj5ATU1IVi7BRY37dKv1j9dhGgIqdfFkIpMDIQ" original_src_selector:"app == 'podinfo'" rule_id:"Vpf0HAInm19ya0fI" > original_selector:"(component == \"podtato-head-entry\" && projectcalico.org/namespace == \"podtato\")" >
2025-03-15 22:10:52.251 [INFO][89] felix/int_dataplane.go 2041: Received *proto.WorkloadEndpointUpdate update from calculation graph msg=id:<orchestrator_id:"k8s" workload_id:"podtato/podtato-head-entry-68f945f584-hdkfj" endpoint_id:"eth0" > endpoint:<state:"active" name:"cali19d64cc69db" profile_ids:"kns.podtato" profile_ids:"ksa.podtato.default" ipv4_nets:"192.168.41.196/32" tiers:<name:"default" ingress_policies:"podtato/default.log-and-deny-ingress" egress_policies:"podtato/default.log-and-deny-ingress" default_action:"Deny" > >

Clean up by deleting the network policy:

kubectl -n podtato delete networkpolicies.projectcalico.org default.log-and-deny-ingress