Kubernetes Networking Deep Dive

Network Security

Demo Cilium Network Policies

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

  1. Demo App Overview
  2. Default Kubernetes NetworkPolicy
  3. Cilium Layer 3 Policy
  4. Cilium Layer 4 Policy
  5. Cilium Layer 7 HTTP Policy
  6. Adding an API Key Header
  7. 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.

kubectl get all
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

  1. 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!
    
  2. 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...
    

Next Step

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"}

5. Adding an API Key Header

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

StageLayerFileDescription
KubernetesL3demo-netpolPod selector + ingress from app=admin
CiliumL3cilium-l3.yamlSame selector using Cilium CRD
CiliumL4cilium-l4.yamlRestrict to TCP port 80
CiliumL7cilium-l7.yamlAllow only GET /healthz & /api
CiliumL7+Hcilium-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!

Watch Video

Watch video content

Practice Lab

Practice lab

Previous
CNI Network Policies Overview