Cilium Network Policy: Kubernetes NetworkPolicy

When people first start working with Cilium policies, the easiest way to understand them is to group them into two simple ideas:

  1. Who can talk to what?
  2. What they’re allowed to do once connected?

That mental model maps directly to how Cilium builds policy enforcement—from basic workload isolation all the way up to application-aware HTTP filtering.

If you already think in terms of namespace rules and Layer 7 rules like HTTP GET/POST like we did in Istio, you’re already on the right track. Cilium simply expands that model into something much more powerful and much more granular.

Table of Contents

Kubernetes NetworkPolicy

This is the standard Kubernetes-native policy object. That means you can write standard Kubernetes NetworkPolicy objects and still have them enforced by Cilium, without changing the API.

It provides basic network controls such as:

  • ingress and egress rules
  • pod selectors
  • namespace selectors
  • port restrictions

This is useful for simple traffic control such as allowing one namespace to reach another or limiting traffic to specific ports.

However, native Kubernetes NetworkPolicy is limited to Layer 3 and Layer 4. It does not understand application-layer protocols like HTTP, DNS, or gRPC, and it does not support Cilium-specific features like FQDN filtering or deny rules.

Example

Let’s use the echo pod in this post.

Policy

This example allows only pods in the universe namespace to access pods labeled app=echo in the echo namespace on TCP port 80.

allow-universe-to-echo.yaml

 1apiVersion: networking.k8s.io/v1
 2kind: NetworkPolicy
 3metadata:
 4  name: allow-universe-to-echo
 5  namespace: echo
 6spec:
 7  podSelector:
 8    matchLabels:
 9      app: echo
10
11  policyTypes:
12    - Ingress
13
14  ingress:
15    - from:
16        - namespaceSelector:
17            matchLabels:
18              kubernetes.io/metadata.name: universe
19      ports:
20        - protocol: TCP
21          port: 80

Apply policy in echo namespace.

1kubectl apply -f allow-universe-to-echo.yaml

App

And deploy curl app in both universe and galaxy namespace.

1kubectl run curl \
2  --image=curlimages/curl:latest \
3  --restart=Never \
4  --labels="app=curl" \
5  -n universe \
6  --command -- sleep infinity
1kubectl run curl \
2  --image=curlimages/curl:latest \
3  --restart=Never \
4  --labels="app=curl" \
5  -n galaxy \
6  --command -- sleep infinity

Verify

Verify, notice that curl pod in galaxy namespace cannot reach echo pod in echo namespace.

1kubectl exec -it curl -n universe -- sh
2~ $ curl -m 5 -s -o /dev/null -w "%{http_code}\n" echo.echo.svc.cluster.local
3200
4
5kubectl exec -it curl -n galaxy -- sh
6~ $ curl -m 5 -s -o /dev/null -w "%{http_code}\n" echo.echo.svc.cluster.local
7000

Hubble

We can also confirm this in Cilium using hubble.

 1kubectl exec -it cilium-k6cb4 -n kube-system -- hubble observe
 2Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) policy-verdict:none TRAFFIC_DIRECTION_UNKNOWN ALLOWED (TCP Flags: SYN)
 3Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: SYN)
 4Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) <- echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: SYN, ACK)
 5Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK)
 6Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
 7Apr 27 11:26:11.993: universe/curl:33156 (ID:93339) <- echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK, PSH)
 8Apr 27 11:26:11.994: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
 9Apr 27 11:26:11.994: universe/curl:33156 (ID:93339) <- echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK, FIN)
10Apr 27 11:26:11.995: universe/curl:33156 (ID:93339) -> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) to-endpoint FORWARDED (TCP Flags: ACK)
1kubectl exec -it cilium-k6cb4 -n kube-system -- hubble observe
2Apr 27 11:27:13.848: galaxy/curl:55360 (ID:102210) <> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) policy-verdict:none TRAFFIC_DIRECTION_UNKNOWN DENIED (TCP Flags: SYN)
3Apr 27 11:27:13.848: galaxy/curl:55360 (ID:102210) <> echo/echo-5dc8fd6498-kfvj6:80 (ID:71969) Policy denied DROPPED (TCP Flags: SYN)

Notice the ID tag on each pod, to make debugging and observability easier we can extract it using command below.

1kubectl get cep curl -n universe -o jsonpath='{.status.identity.id}'
293339
3
4kubectl get cep curl -n galaxy -o jsonpath='{.status.identity.id}'
5102210

Now we can use hubble like this.

1kubectl exec -it cilium-k6cb4 -n kube-system -- hubble observe 93339
2kubectl exec -it cilium-k6cb4 -n kube-system -- hubble observe 102210

UI

You can also observe this in the Hubble UI. Port-forward the service or attach it to an IP pool.

1kubectl port-forward svc/hubble-ui 8080:80 -n kube-system
Hubble UI