Cilium uses eBPF to enforce Layer 7 policies directly on network packets, allowing fine-grained control over HTTP and gRPC traffic without traditional proxies.
Let’s see Cilium in action with some HTTP traffic. Imagine two services, frontend and backend. We want to allow frontend to only make GET requests to /api/v1/users on backend.
First, ensure you have Cilium installed with the hubble and policy-manager components enabled. You’ll also need your services deployed in Kubernetes.
apiVersion: cilium.io/v1alpha1
kind: CiliumNetworkPolicy
metadata:
name: frontend-to-backend-users
spec:
endpointSelector:
matchLabels:
app: frontend
ingress:
- fromEndpoints:
- matchLabels:
app: backend
toPorts:
- ports:
- number: 8080 # The port backend listens on
protocol: TCP
rules:
http:
- method: "GET"
path: "/api/v1/users"
When frontend tries to POST to /api/v1/users, or GET to /api/v1/products, Cilium, running eBPF on the node where frontend is scheduled, intercepts the packets. It decodes the HTTP request, checks the method and path against the policy, and drops the packet if it doesn’t match. This happens before the packet even reaches the backend service’s network stack, let alone a kube-proxy or Ingress controller.
The core problem this solves is the gap between network segmentation (Layer 3/4) and application-aware security. Traditional firewalls and NetworkPolicies operate at the IP and port level. They can block all traffic between two pods, but they can’t distinguish between a legitimate GET /users request and a malicious POST /admin request if both are on the same port. Cilium, by leveraging eBPF, can inspect the actual HTTP or gRPC payload.
Internally, Cilium uses its bpf_l7_proxy functionality. When L7 policy is enabled, Cilium loads eBPF programs into the kernel. These programs attach to the network stack of the pod’s network namespace. For outgoing traffic, the eBPF program intercepts packets destined for other pods. It reconstructs enough of the HTTP or gRPC request to check against the defined rules. If the traffic is allowed, it’s forwarded. If it’s denied, it’s dropped at the earliest possible point. For incoming traffic, the same eBPF program intercepts packets arriving at the pod’s network interface and applies the policy.
The endpointSelector defines which pods this policy applies to (in this case, pods with the label app: frontend). The ingress section specifies rules for traffic entering the selected pods. Here, we’re allowing traffic from pods labeled app: backend on port 8080. The rules.http block is where the L7 magic happens. We specify the exact method and path that are permitted.
For gRPC, the configuration is similar, but you’d use rules.grpc with service and method fields. For example, to allow a GetUsers call on the UserService:
rules:
grpc:
- service: UserService
method: GetUsers
You can also use wildcards: path: "/api/v1/*" or method: "*" to allow any method on a specific path. You can even combine rules. For instance, allow GET /api/v1/users and POST /api/v1/users/create.
The most surprising thing is that this L7 inspection doesn’t require a separate, dedicated proxy like Envoy or Nginx running alongside your application. Cilium’s eBPF programs perform this inspection directly within the Linux kernel. This means you avoid the overhead, complexity, and potential single point of failure associated with sidecar proxies. The policy enforcement is distributed and happens at line rate.
The next thing you’ll want to explore is how to integrate this with Hubble for visibility, allowing you to see which L7 requests are being allowed or denied by your policies, and how to use L7 rate limiting to protect your services from overload.