Ecstaticloud
Initializing0%
Ecstaticloud Logo
Back to Insights
CybersecurityOctober 8, 2026

Zero-Trust in Kubernetes: Implementing eBPF-Powered Security Observability with Cilium

Discover how to leverage eBPF and Cilium to achieve kernel-level network visibility and enforce strict zero-trust policies across your Kubernetes clusters. Learn practical configurations to eliminate sidecar overhead while drastically strengthening your container security posture.

In traditional Kubernetes networking, once an attacker gains a foothold inside a pod, the flat cluster network becomes an open highway for lateral movement. The historic answer to this challenge was injecting sidecar proxies into every application pod to manage traffic, enforce mutual TLS (mTLS), and inspect packet flows.

However, as workloads scale into thousands of pods across hundreds of nodes, the sidecar pattern reaches its limits. Sidecar proxies introduce severe memory bloat, measurable CPU overhead, increased latency from context switching between user and kernel space, and operational friction from cycle-heavy application injection.

To implement a true Zero-Trust Network Architecture (ZTNA) without crushing cluster performance, we must push network visibility and policy enforcement directly into the Linux kernel. This is where eBPF (Extended Berkeley Packet Filter) and Cilium fundamentally transform Kubernetes security.


Why eBPF Supersedes the Sidecar and Iptables Model

To understand Cilium’s paradigm shift, we must look at how standard Kubernetes networking operates:

  1. iptables Scalability Limits: Default kube-proxy uses iptables to route traffic. As services grow, iptables rules scale sequentially ($O(N)$ complexity). Processing a packet requires traversing thousands of rules sequentially, leading to high CPU usage and packet processing delay.
  2. Sidecar Overhead: Injecting an Envoy sidecar into every pod means every request traverses the TCP/IP stack multiple times (Pod Network Namespace $\rightarrow$ VETH $\rightarrow$ Host Namespace $\rightarrow$ Envoy Proxy $\rightarrow$ Host Namespace $\rightarrow$ Network Device). This imposes a heavy resource tax and adds milliseconds of latency to inter-service calls.
Legacy Sidecar Flow:
[ Pod A App ] -> (Loopback) -> [ Envoy Sidecar ] -> (veth) -> [ Host Kernel ] -> Network
                                                                                     |
[ Pod B App ] <- (Loopback) <- [ Envoy Sidecar ] <- (veth) <- [ Host Kernel ] <------+
(Multiple context switches between Kernel and User space)

eBPF bypasses these bottlenecks by allowing sandboxed programs to execute inside the Linux kernel dynamically without mutating kernel source code or loading kernel modules.

eBPF / Cilium Direct Flow:
[ Pod A App ] -------------------> [ eBPF Socket / TC Hook ] ------------------> Network
                                               |                                    |
[ Pod B App ] <------------------- [ eBPF Socket / TC Hook ] <----------------------+
(In-kernel routing: Zero sidecar, Zero context-switching overhead)

Cilium hooks directly into kernel execution points like Traffic Control (TC), cgroups, and socket layers (sockmap). Traffic is intercepted, evaluated against security policies stored in BPF maps, and routed or dropped immediately in the kernel before an application or user-space proxy even sees the bytes.


The Zero-Trust Pillars with Cilium

Implementing Zero-Trust in Kubernetes requires adhering to three core principles:

  • Always Verify, Never Trust: Default-deny posture for all ingress and egress across microservices, namespaces, and cluster boundaries.
  • Least Privilege Access: Restrict communication not just by IP/Port (L3/L4), but by application layer context (L7 protocols, HTTP paths, API methods, and gRPC calls).
  • Complete Observability: Continuous, deep inspection of all network interactions to detect anomalies and verify compliance dynamically.

Step 1: Deploying Cilium with eBPF Kube-Proxy Replacement

To unlock the full performance and security capabilities of Cilium, we will replace kube-proxy entirely and enable Hubble—Cilium’s distributed network and security observability engine.

Prerequisites

  • A Kubernetes cluster (v1.26+) running a modern Linux kernel (5.4+ recommended, 5.10+ ideal for full L7 eBPF offload).
  • Helm v3 installed.

Helm Deployment Architecture

Deploy Cilium with eBPF-based kube-proxy replacement enabled:

helm repo add cilium https://helm.cilium.io/
helm repo update

helm install cilium cilium/cilium \
  --version 1.15.1 \
  --namespace kube-system \
  --set kubeProxyReplacement=true \
  --set k8sServiceHost=<KUBE_API_SERVER_IP> \
  --set k8sServicePort=6443 \
  --set bpf.masquerade=true \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,icmp,http}"

To verify that eBPF programs are successfully loaded into the host kernel, run:

kubectl -n kube-system exec -it ds/cilium -- cilium status --verbose

Look for Kube-Proxy Replacement: Strict and verify BPF maps are mounted at /sys/fs/bpf.


Step 2: Implementing Default-Deny Policies (Zero-Trust Baseline)

In a Zero-Trust architecture, unmanaged inter-service communication must be blocked by default. Standard NetworkPolicy objects can accomplish basic network segmentation, but CiliumNetworkPolicy expands this by providing layer 7 capabilities, FQDN filtering, and eBPF-accelerated processing.

Establishing the Default-Deny Security Guardrail

Apply a cluster-wide policy or namespace-bound policy to enforce default deny for both Ingress and Egress traffic:

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  description: "Enforce strict Zero-Trust baseline default-deny posture for namespace production."
  endpointSelector: {} # Selects all endpoints in the namespace
  ingress:
    - {} # Empty array with no allows means drop all incoming traffic
  egress:
    - {} # Empty array with no allows means drop all outgoing traffic

Once applied, all pods inside the production namespace will be completely isolated. Now, we explicitly craft policies to grant least-privilege permissions.


Step 3: Layer 3 / Layer 4 / Layer 7 Policy Enforcement

Let's trace a practical example: A frontend microservice needs to invoke an order-service via HTTP GET /orders and POST /orders, while blocking access to sensitive paths like /admin or non-HTTP protocols. Additionally, order-service needs external egress to a payment gateway endpoint (api.stripe.com) over TLS port 443.

[ Frontend Pod ] 
      |
      | (HTTP GET/POST on /orders ONLY) -> [ L7 eBPF / Envoy Filter ]
      v
[ Order Service Pod ] 
      |
      | (HTTPS to api.stripe.com ONLY)
      v
[ External: Stripe API ]

Writing the Comprehensive CiliumNetworkPolicy

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: allow-frontend-to-order-and-stripe
  namespace: production
spec:
  endpointSelector:
    matchLabels:
      app: order-service

  # Ingress Rules: Controls incoming traffic into 'order-service'
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: frontend
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP
          rules:
            http:
              - method: "GET"
                path: "/orders.*"
              - method: "POST"
                path: "/orders.*"

  # Egress Rules: Controls outgoing traffic from 'order-service'
  egress:
    # Allow DNS Resolution via CoreDNS (Required for FQDN Egress)
    - toEndpoints:
        - matchLabels:
            k8s:io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - port: "53"
              protocol: UDP
          rules:
            dns:
              - matchPattern: "*"
    # Allow Secure External API Egress to Stripe
    - toFQDNs:
        - matchName: "api.stripe.com"
      toPorts:
        - ports:
            - port: "443"
              protocol: TCP

What Happens at the Kernel Level?

  1. L3/L4 Traffic (IP/Port): Intercepted directly by the eBPF TC hook. If the traffic matches the label selectors (app: frontend $\rightarrow$ app: order-service on port 8080), it is passed forward. If not, the packet is instantly dropped at the kernel boundary before allocating socket buffers.
  2. L7 Traffic (HTTP Path/Method Parsing): Cilium dynamically redirects allowed L3/L4 flows to an internal embedded Envoy proxy instance managed directly by the local Cilium Agent (no sidecar required). Envoy parses the HTTP headers, validates the verb/path, and forwards the packet back to the container namespace via BPF maps.

Step 4: Security Observability in Action with Hubble CLI

One of the largest hurdles in implementing Zero-Trust is policy debugging: How do you write policies without breaking production traffic?

Cilium’s Hubble captures kernel-level packet drops, allows paths, and HTTP status codes in real-time without introducing continuous overhead.

Inspecting Real-Time Policy Drops

To audit denied traffic in your cluster, use the Hubble CLI:

# Observe dropped traffic in real time across the production namespace
hubble observe --namespace production --verdict DROP --follow

Example Output:

TIMESTAMP            SOURCE                    DESTINATION                   TYPE          VERDICT
Oct 24 14:22:01.102  production/frontend-x9z   production/order-service-a1   http-request  DROPED (Policy denied)
  Summary: HTTP/1.1 DELETE /orders/1234

In this real-world flow, Hubble informs us instantly that pod frontend-x9z attempted an unauthorized DELETE request to order-service-a1, triggering an eBPF policy drop.

Deep Packet Context and DNS Observability

Trace external FQDN resolutions to ensure applications are not leaking data or making out-of-bounds egress requests:

hubble observe --namespace production --pod-name order-service-a1 --type dns

Example Output:

TIMESTAMP            SOURCE                        DESTINATION               SUMMARY
Oct 24 14:25:30.401  production/order-service-a1   kube-system/kube-dns-88   Query api.stripe.com. A?
Oct 24 14:25:30.405  kube-system/kube-dns-88       production/order-service  Response A 151.101.65.138 (TTL 60)

By leveraging Hubble, security teams can convert current traffic flows into Cilium Network Policies automatically using dry-run modes (--set policy.auditMode=true), dramatically decreasing policy development cycles.


Performance Comparison: Sidecar vs. eBPF Zero-Trust

| Metric | Legacy Envoy Sidecar Mesh | eBPF + Cilium Architecture | | :--- | :--- | :--- | | Data Path Processing | Context switches between kernel & user space per hop | Direct in-kernel lookup via eBPF BPF maps | | CPU Footprint | Scales linearly with pod count (~50-100m CPU per pod) | Fixed overhead per node (~1-2% total host CPU) | | Memory Footprint | ~50MB - 150MB per sidecar proxy injected | ~100MB per Node (Cilium Agent only) | | Latency Penalty | +1.5ms - 4.5ms per service hop | +0.05ms - 0.2ms per service hop | | Security Boundary | Container Namespace (vulnerable to container escapes) | Host Kernel Ring 0 Protection |


Architectural Best Practices for Production

  1. Leverage Kernel 5.10+: Ensure your worker node OS uses a modern Linux Kernel (e.g., Ubuntu 22.04 LTS, Bottlerocket, or Flatcar) to enable eBPF Host Routing, which completely bypasses the host network stack for local pod-to-pod communications.
  2. Incrementally Enforce via Audit Mode: Enable Audit Mode during initial enforcement to validate traffic flows without dropping critical data packets:
    metadata:
      annotations:
        cilium.io/policy-mode: "audit"
    
  3. Isolate Sensitive Workloads with Node-Local BPF Maps: In high-density clusters, optimize BPF map capacities within Helm values to avoid map overflow errors (bpf-map-dynamic-size-ratio: "0.0025").
  4. Implement Cryptographic Identity with SPIFFE/SPIRE & Cilium WireGuard: When Zero-Trust requires encrypted transport along with L3-L7 policy enforcement, rely on Cilium's transparent, eBPF-driven WireGuard integration rather than user-space mTLS overhead:
    helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values --set encryption.enabled=true --set encryption.type=wireguard
    

Conclusion

Building a Zero-Trust architecture in Kubernetes no longer requires trading performance for protection. By delegating traffic control, encryption, and security observability to the Linux kernel via eBPF and Cilium, cloud engineers can eliminate the resource drain of traditional sidecars while acquiring deep observability into every flow across their infrastructure.

Adopting an eBPF-native security posture equips your cloud platforms with dynamic, real-time defenses—yielding a lean, scalable, and resilient runtime defense mechanism capable of withstanding modern cloud-native threats.