Hardening Kubernetes Control Planes against SSRF: Configuring Cilium Egress Gateways with SPIFFE Identities
Secure outbound traffic and eliminate SSRF vectors in Kubernetes by binding Cilium Egress Gateways to cryptographically attested SPIFFE identities.
In production Kubernetes clusters, Server-Side Request Forgery (SSRF) represents one of the most high-severity vectors for privilege escalation and data exfiltration. A single compromised application workload can probe internal cluster services, query the cloud metadata service (169.254.169.254) to steal IAM credentials, or abuse whitelisted egress routes to exfiltrate database contents to external command-and-control servers. Traditional network firewalls and basic Kubernetes NetworkPolicies rely on ephemeral IP addresses and easily mutable pod labels, offering little protection when an attacker obtains node-level execution or patches pod metadata. This post details how to close these gaps by binding Cilium Egress Gateways to cryptographically verified SPIFFE identities issued by SPIRE, ensuring that egress paths to critical external APIs are restricted to authenticated workloads and completely immune to IP and label-spoofing attacks.
The SSRF Threat Model in Production Kubernetes
Server-Side Request Forgery occurs when an attacker forces a server-side application to make arbitrary HTTP or TCP requests to internal or external destinations. In a standard Kubernetes environment, this threat is amplified due to the shared-network nature of the container runtime and the presence of highly sensitive internal endpoints.
The Cloud Metadata Server (IMDS)
The most common and devastating SSRF target in cloud-hosted Kubernetes (AWS, GCP, Azure) is the Link-Local Instance Metadata Service (IMDS) located at 169.254.169.254. Under AWS IMDSv1, a simple HTTP GET request from any container on the host retrieves the IAM role credentials of the underlying EC2 node. This role often possesses broad permissions, such as reading from S3 buckets, writing to DynamoDB, or modifying Route53 records.
While AWS IMDSv2 introduces a session-oriented token flow requiring a PUT request to generate a cryptographic token, it is not a silver bullet. If a workload runs a complex proxy, PDF renderer, or API integration wrapper that allows HTTP header injection or custom HTTP methods, an attacker can still fetch the token and bypass IMDSv2. Furthermore, in GCP, the metadata service requires the header Metadata-Flavor: Google. Although this restricts naive SSRF (such as browser-like GET redirections), any vulnerability that permits complete control over outbound headers exposes the cluster to service account token theft.
The Kubernetes API Server and Kubelet API
If an attacker compromises a pod, the local service account token is automatically mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. An SSRF vulnerability can be exploited to coerce the workload into querying the internal API server (https://kubernetes.default.svc) or the node-level Kubelet API (https://<node-ip>:10250) on behalf of the attacker. If the service account is over-privileged, the attacker can list resources, read Secrets, or run commands inside other containers.
Egress Bypasses and Firewall Poisoning
Many enterprise architectures require that workloads accessing third-party APIs (such as payment gateways, credit bureaus, or ERP databases) route traffic through a NAT Gateway with a static, whitelisted IP. In Kubernetes, this is achieved using Cilium Egress Gateways, which route outbound traffic through dedicated nodes and apply Source NAT (SNAT) to masquerade the pod’s IP.
However, standard egress routing rules select source pods using declarative Kubernetes labels (e.g., app: payment-processor). If an attacker compromises a frontend pod and has permissions to patch labels (or exploits a compromised CI/CD pipeline), they can append the app: payment-processor label to their malicious container. Instantly, Cilium’s CNI routes the malicious traffic through the egress gateway, allowing the attacker to masquerade as the payment processor and exfiltrate data directly through the whitelisted IP to a malicious external host.
Deconstructing Cilium Egress Gateways
To understand how to secure egress traffic, we must look at how Cilium manages routing at the kernel level using eBPF (Extended Berkeley Packet Filter).
When a pod attempts to send a packet to an external IP, the traffic traverses the pod’s virtual ethernet (veth) interface. Cilium attaches eBPF programs (such as bpf_lxc.c) to the tc (traffic control) ingress and egress hooks of these interfaces. These eBPF programs monitor and manipulate packet headers directly in the kernel space.
In a standard egress configuration, Cilium evaluates the packet against the CiliumEgressGatewayPolicy (CEGP) Custom Resource. The policy consists of:
- Source Selectors: Matches pods based on namespace and pod labels.
- Destination CIDRs: Defines the external IP ranges destined for routing.
- Egress Gateway Node: Specifies the cluster node responsible for handling the egress traffic.
- Masquerade IP: The static egress IP applied to the traffic.
If the packet matches the source pod selector and the destination CIDR, the eBPF program on the local worker node intercepts it. Instead of routing the packet via the default node gateway, the eBPF program encapsulates the packet in a VXLAN or Geneve tunnel header and forwards it directly to the designated Egress Gateway Node. The gateway node decapsulates the packet, replaces the source pod’s IP address with the static egressIP using SNAT, and routes it out to the physical network.
While eBPF enforces this routing at near-line speed, the authorization mechanism relies entirely on the Kubernetes control plane’s state. If the API server’s metadata is manipulated or if container network interfaces are misconfigured, the gateway becomes vulnerable to lateral movement. To establish true zero-trust security, we must decouple the source identification from volatile Kubernetes labels and transition to cryptographic identities.
The Zero-Trust Bridge: SPIFFE/SPIRE Workload Attestation
SPIFFE (Secure Production Identity Framework for Everyone) defines a standard for cryptographically identifying workloads in dynamic heterogeneous environments. SPIRE (the SPIFFE Runtime Environment) is the implementation that handles identity issuance.
Kernel-Level Workload Attestation
Unlike Kubernetes, which trusts the API server’s state, SPIRE verifies a workload’s identity dynamically using OS-level attributes. This process is called Workload Attestation.
When a pod is scheduled, the SPIRE Agent running as a DaemonSet on the node monitors container creation. It exposes a Unix Domain Socket (UDS) at /run/spire/sockets/agent.sock. The Cilium Agent communicates with this socket using the SPIFFE Workload API and SPIRE Delegation API.
The attestation flow proceeds as follows:
- The container initiates a socket connection.
- The SPIRE Agent queries the host kernel using
getsockoptwithSO_PEERCREDto extract the caller’s process ID (PID), User ID (UID), and Group ID (GID). - The SPIRE Agent inspects the
/proc/<pid>/cgrouppath to extract the container ID. - The SPIRE Agent queries the container runtime (e.g.,
containerdorCRI-O) using the container ID to fetch metadata: the Kubernetes pod name, namespace, service account, and the cryptographic hash of the container image. - This metadata is validated against pre-registered SPIFFE registration entries. If the attributes match, the SPIRE Agent generates a SPIFFE Verifiable Identity Document (SVID)—an X.509 certificate—representing the workload’s SPIFFE ID (e.g.,
spiffe://prod.muklis.dev/ns/payment-system/sa/payment-processor).
Integrations with Cilium eBPF
Cilium acts as a trusted SPIFFE delegate. When SPIRE attests a pod, the Cilium Agent retrieves the corresponding SPIFFE identity and maps it to a Cilium Security Identity.
Cilium translates the cryptographic SPIFFE ID into a special policy label:
spiffe:<trust-domain>/ns/<namespace>/sa/<service-account>
Crucially, this label is generated locally by the cilium-agent and injected directly into the kernel’s eBPF maps (cilium_endpoints and cilium_ipcache). It is never pushed to the Kubernetes API server as a standard pod metadata label. An attacker attempting to modify pod labels via kubectl patch or by exploiting an RBAC vulnerability cannot alter this label. The eBPF datapath enforces policies based on this read-only, cryptographically verified identity, completely preventing label-spoofing and IP-spoofing.
Step-by-Step Implementation Guide
This section outlines how to configure Cilium with SPIRE, register workloads, block SSRF to the metadata server, and secure Cilium Egress Gateways using attested SPIFFE identities.
Step 1: Install Cilium with SPIRE Support
You must deploy Cilium with the mutual authentication and SPIFFE/SPIRE integrations enabled. Use the following Helm configuration values:
Step 2: Register the Workload in SPIRE
To ensure the SPIRE Server issues identities based on strict cryptographic and kernel attributes, create a registration entry. In addition to namespace and service account checks, you should enforce container image hash validation to prevent container execution hijacking.
Step 3: Enforce SSRF Isolation
Use a CiliumNetworkPolicy to restrict the payment processor pod’s outbound traffic. This policy blocks direct egress to the host metadata IP (169.254.169.254) and restricts outbound access exclusively to CoreDNS and the Stripe API CIDR.
Step 4: Configure the Cilium Egress Gateway Policy
Rather than using standard Kubernetes labels, write a CiliumEgressGatewayPolicy that selects the source workloads using the cryptographically verified SPIFFE label injected by Cilium.
Step 5: Restrict Gateway Access via SPIFFE-Based Network Policy
To ensure that only workloads containing the attested SPIFFE identity can access egress paths, construct an egress policy targeting the SPIFFE label directly.
Step 6: Verify and Debug in Production
To confirm that SPIFFE identity mappings are active and matched in the kernel’s eBPF structures, use the following debugging commands on the worker nodes:
Failure Modes and Production Considerations
Running a combined Cilium and SPIFFE/SPIRE architecture in high-traffic production environments introduces specific failure modes that system engineers must anticipate and mitigate.
SPIRE Socket Availability and DaemonSet Startup Latency
When a pod starts up, its container runtime must mount the SPIRE Agent’s Unix Domain Socket. If the SPIRE Agent DaemonSet undergoes a rolling update or is descheduled, the UDS will become unavailable.
- Failure Mode: Workloads fail to boot or timeout during startup because they cannot obtain their SVID.
- Mitigation: Implement a fallback configuration using
spiffe-csi-driverto mount the socket directory. Set the container’s readiness probes to depend on the local SPIFFE identity acquisition. Ensure the SPIRE Agent DaemonSet runs with high priority (priorityClassName: system-node-critical).
eBPF Map Limit Overflows (map-max-entries)
Cilium stores security identities and IP allocations in fixed-size BPF maps. In highly dynamic clusters where thousands of pods spin up and down (such as batch processing or autoscaling microservices), the BPF IPcache map can overflow.
- Failure Mode: Pods fail to resolve identities, causing egress packets to be dropped with
Policy Deniederrors. - Mitigation: Tune the Cilium Helm values to scale up the map limits:
bpf: mapNameProperties: cilium_ipcache: max_entries: 524288 cilium_auth: max_entries: 262144
Egress Gateway Failover Latency
If the node acting as the Egress Gateway dies, Cilium must redirect traffic to a healthy gateway node.
- Failure Mode: Traffic to external APIs drops for up to 10 seconds while the control plane updates the gateway node paths and updates the eBPF maps on worker nodes.
- Mitigation: Configure high availability by defining multiple gateway nodes under
egressGatewaysinside your policy, or peer the gateway nodes using BGP (via Cilium BGP Control Plane) to allow the physical network to handle immediate failovers.
Certificate Expiry and Renewal Margins
SPIFFE SVIDs are short-lived certificates, typically expiring within 1 to 12 hours.
- Failure Mode: If the connection between the SPIRE Agent and SPIRE Server breaks (due to network partition or database locks), the agent cannot renew SVIDs. Once the local SVID expires, Cilium drops all associated traffic.
- Mitigation: Configure the SPIRE Server to alert when SVID renewal rates drop. Implement Prometheus monitoring metrics tracking the remaining lifetime of active certificates (
spire_agent_svid_ttl_seconds).
Conclusion
Securing outbound traffic in Kubernetes requires moving past standard IP-based perimeter models and mutable API metadata. By integrating Cilium Egress Gateways with cryptographically verified SPIFFE identities, you enforce a zero-trust model where every outbound packet is verified using kernel-level attestation. Compromised workloads cannot bypass these policies by spoofing IP addresses or patching Kubernetes labels. The combination of eBPF-driven routing and SPIFFE-based workload attestation ensures your control plane and data paths remain resilient against SSRF and unauthorized egress attacks.