See exactly what every pod talks to — domains, paths, headers, IPs — then allow or deny it.
Attributed per pod. Enforced in the kernel.
Install · Configuration · Egress policies · Roadmap · Images
ebfw is a single, node-level eBPF agent that shows — and enforces — what every
pod on a Kubernetes node is allowed to reach. One cgroup_skb program at the
node's root cgroup sees every pod's outbound DNS, TLS SNI, HTTP, and new TCP
connections; an SSL_write uprobe recovers HTTPS request paths before
encryption. Every event is attributed to the originating pod (namespace/name).
The same in-kernel hooks then allow or deny egress per pod by
domain / IP / CIDR / port, driven by Kubernetes-native EgressPolicy CRDs. It
also sees HTTP paths and headers and lets policy match on method and path
(evaluated today; L7 enforcement is on the roadmap).
- One
cgroup_skb/egressprogram at the node's root cgroup v2 sees egress from every pod on the node — DNS, TLS ClientHello SNI, plaintext HTTP, and new TCP connections — no per-pod sidecar. - An
SSL_writeuprobe reads HTTPS request plaintext before encryption, recovering paths the packet layer can't see. Auto-discovered per container's libssl, live (no sampling). - Attributed per pod in-kernel via the originating cgroup id, enriched to
namespace/nameby a node-scoped Pods informer. The same maps carry policy verdicts back to the kernel for enforcement.
flowchart LR
pods["Pods on node"]
subgraph kernel["in-kernel (eBPF)"]
egress["cgroup_skb/egress<br/>DNS, TLS SNI, HTTP, CONNECT"]
uprobe["SSL_write uprobe<br/>HTTPS path + headers"]
end
agent["ebfw agent (Go)<br/>attribute + policy engine"]
informer["k8s Pods informer"]
out["text / JSON<br/>Prometheus /metrics"]
pods --> egress
pods --> uprobe
egress -- "+ cgroup id" --> agent
uprobe -- "+ cgroup id" --> agent
informer -- "namespace/name" --> agent
agent --> out
agent -. "allow / deny verdicts" .-> kernel
DNS 10.42.0.9 ? example.com (TypeA) pod=default/probe
TLS 10.42.0.9 -> example.com (104.20.23.154:443) pod=default/probe
HTTP 10.42.0.9 -> GET example.com/foo/bar pod=default/probe
HTTPS [pid=2550689 curl] GET example.com/secret/path?token=abc123 pod=default/probe
CONNECT 10.42.0.9 -> 104.20.23.154:443 pod=default/probe
Or structured JSON (EBFW_OUTPUT=json, one object per line):
{"ts":"2026-06-28T09:14:01Z","kind":"https","domain":"example.com","method":"GET","path":"/secret/path","pid":2550689,"comm":"curl","pod":{"namespace":"default","name":"probe","uid":"be31e934-…","container":"ad68e835…","qos":"besteffort","node":"k3d-ebfw-server-0"}}Declare egress as Kubernetes resources — EgressPolicy (namespaced) and
ClusterEgressPolicy (cluster-wide) — and the agent enforces them in three modes:
off (observe), log (annotate verdicts, no drops), or enforce (drop denied
egress). A defaultAction: Deny plus a podSelector locks a labeled set of pods
to an allowlist. See docs/egresspolicy.md.
helm install ebfw ./helm/ebfw -n ebfw --create-namespace \
--set agent.enforceMode=log # observe verdicts first; flip to enforce when ready
kubectl apply -f config/samples/ebfw_v1_egresspolicy.yamlFull guide → docs/install.md.
Cilium is a CNI — it owns your cluster's network. ebfw rides alongside whatever
you already run. It attaches at the node's root cgroup, so you can drop the
DaemonSet on any node (any CNI, or no Kubernetes at all), watch egress in log
mode, and pull it back out with zero effect on connectivity — it never touches the
dataplane.
The headline difference: ebfw reads HTTPS request paths and headers from an
SSL_write uprobe — before encryption, with no proxy and no TLS MITM. Cilium
needs a terminating Envoy and injected certs to see the same thing.
ebfw is the lightweight egress firewall + per-pod L7 visibility layer; Cilium is the full networking platform (and does L7 enforcement today, which ebfw doesn't yet). Full feature-by-feature breakdown → docs/comparison.md.
ebfw isn't tied to Kubernetes. The same agent — as a privileged container or a single host binary — watches and enforces egress for every container on a plain Docker / containerd host, driven by a YAML policy file instead of the CRDs (same visibility, same node-wide enforcement, no API server or operator). See Run standalone.
MIT © dvrkn.
Built with the eBPF skill for
hook/map/verifier guidance and cilium/ebpf
(ebpf-go, MIT) for the loader.