In this tutorial, we’ll secure a sample application using Cilium Network Policies. We’ll progress from a default Kubernetes NetworkPolicy (Layer 3) to a full L7 HTTP policy with header-based access control.
Table of Contents
Demo App Overview
Default Kubernetes NetworkPolicy
Cilium Layer 3 Policy
Cilium Layer 4 Policy
Cilium Layer 7 HTTP Policy
Adding an API Key Header
Further Reading
Demo App Overview
Our demo application runs as a single Pod with two containers listening on ports 5000 and 80 . It exposes two corresponding ClusterIP Services.
NAME READY STATUS RESTARTS AGE
pod/demo-deployment-7ccd685fcc-7z9wf 2/2 Running 0 5m
NAME TYPE CLUSTER-IP PORT(S) AGE
service/app-svc-5000 ClusterIP 10.111.51.97 5000/TCP 5m
service/app-svc-80 ClusterIP 10.102.122.72 80/TCP 5m
service/kubernetes ClusterIP 10.96.0.1 443/TCP 10m
Both containers serve the same Flask app. We’ll lock down access so only Pods labeled app=admin can communicate.
1. Default Kubernetes NetworkPolicy
We begin with a basic Kubernetes NetworkPolicy named demo-netpol. It selects Pods labeled app=demo and allows ingress from Pods labeled app=admin on all ports .
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : demo-netpol
spec :
podSelector :
matchLabels :
app : demo
ingress :
- from :
- podSelector :
matchLabels :
app : admin
Verifying the Default Policy
Allowed : Pod with app=admin can reach both ports.
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80
# → Have a great day!
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-5000
# → Have a great day!
Denied : Pod without the label is blocked.
kubectl run --rm -i --tty client --image=curlimages/curl \
--restart=Never -- \
curl --connect-timeout 2 app-svc-80
# → curl: (28) Failed to connect...
Before applying Cilium policies, delete the existing Kubernetes NetworkPolicy so that Cilium’s default behavior (allow all) is restored.
kubectl delete networkpolicy demo-netpol
2. Cilium Layer 3 Policy
Create cilium-l3.yaml to reimplement the same L3 selector using Cilium’s CRD:
apiVersion : cilium.io/v2
kind : CiliumNetworkPolicy
metadata :
name : demo-cilium-l3
spec :
endpointSelector :
matchLabels :
app : demo
ingress :
- fromEndpoints :
- matchLabels :
app : admin
Apply and test:
kubectl apply -f cilium-l3.yaml
# Unlabeled Pod → denied:
kubectl run --rm -i --tty client --image=curlimages/curl \
--restart=Never -- \
curl --connect-timeout 2 app-svc-80
# Labeled Pod → allowed:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-5000
# → Have a great day!
3. Cilium Layer 4 Policy
Tighten access to only TCP port 80. Update to cilium-l4.yaml:
apiVersion : cilium.io/v2
kind : CiliumNetworkPolicy
metadata :
name : demo-cilium-l4
spec :
endpointSelector :
matchLabels :
app : demo
ingress :
- fromEndpoints :
- matchLabels :
app : admin
toPorts :
- ports :
- port : "80"
protocol : TCP
kubectl apply -f cilium-l4.yaml
# Port 80 → allowed:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80
# Port 5000 → denied:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-5000
# → curl: (28) Failed to connect...
4. Cilium Layer 7 HTTP Policy
Leverage Cilium’s L7 HTTP inspection to allow only GET /healthz and GET /api. Define cilium-l7.yaml:
apiVersion : cilium.io/v2
kind : CiliumNetworkPolicy
metadata :
name : demo-cilium-l7
spec :
endpointSelector :
matchLabels :
app : demo
ingress :
- fromEndpoints :
- matchLabels :
app : admin
toPorts :
- ports :
- port : "80"
protocol : TCP
rules :
http :
- method : GET
path : /healthz
- method : GET
path : /api
kubectl apply -f cilium-l7.yaml
# Default path → denied:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80
# /api → allowed:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80/api
# /healthz → allowed:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80/healthz
# → {"status":"OK"}
Finally, require an X-API-KEY header for the /api endpoint. Update to cilium-l7-header.yaml:
apiVersion : cilium.io/v2
kind : CiliumNetworkPolicy
metadata :
name : demo-cilium-l7-header
spec :
endpointSelector :
matchLabels :
app : demo
ingress :
- fromEndpoints :
- matchLabels :
app : admin
toPorts :
- ports :
- port : "80"
protocol : TCP
rules :
http :
- method : GET
path : /healthz
- method : GET
path : /api
headers :
- name : X-API-KEY
value : ABC123
kubectl apply -f cilium-l7-header.yaml
# Missing header → denied:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl --connect-timeout 2 app-svc-80/api
# With header → allowed:
kubectl run --rm -i --tty admin --image=curlimages/curl \
--labels app=admin --restart=Never -- \
curl -H "X-API-KEY: ABC123" --connect-timeout 2 app-svc-80/api
# → Have a great day!
Policy Progression
Stage Layer File Description Kubernetes L3 demo-netpolPod selector + ingress from app=admin Cilium L3 cilium-l3.yamlSame selector using Cilium CRD Cilium L4 cilium-l4.yamlRestrict to TCP port 80 Cilium L7 cilium-l7.yamlAllow only GET /healthz & /api Cilium L7+H cilium-l7-header.yamlAdds X-API-KEY header check
Further Reading
With Cilium Network Policies, you have full L3–L7 control to secure your workloads. Experiment with custom rules to fit your security requirements!