Skip to content

PATTERN Cited by 1 source

cGroup-scoped egress firewall (eBPF)

cGroup-scoped egress firewall (eBPF) is the pattern of applying a per-process-set outbound network policy on a host by (1) placing the target processes into a dedicated Linux cGroup, (2) attaching eBPF programs to that cGroup, and (3) having those programs consult userspace-compiled policy (via eBPF maps) to allow, drop, or rewrite outbound connections. The rest of the host — including other processes on the same NIC / same network namespace / same IP — is unaffected.

Canonical production instance: GitHub's deployment-safety firewall against deploy-path circular dependencies (Source: sources/2026-04-16-github-ebpf-deployment-safety).

Core shape

        userspace                              kernel
  ┌────────────────────┐                 ┌─────────────────────┐
  │ policy compiler    │ ── eBPF maps ──►│ cGroup-attached    │
  │ (blocklist, rules) │                 │ eBPF programs      │
  └────────────────────┘                 │  - CGROUP_SKB      │
                                         │  - CGROUP_SOCK_ADDR│
                                         └────────┬───────────┘
                                                  │ attached via
                                                  │ link.AttachCgroup
                                         ┌─────────────────────┐
                                         │ /sys/fs/cgroup/foo  │
                                         │  - process A (in)   │
                                         │  - process B (in)   │
                                         │  ... (out-of-cgroup │
                                         │   processes unaffected)│
                                         └─────────────────────┘

Control plane (userspace) + data plane (cGroup eBPF programs + eBPF maps) is a textbook CP/DP split inside a single process boundary.

Three eBPF building blocks

  1. BPF_PROG_TYPE_CGROUP_SKB — egress (and ingress) packet filter at the cGroup boundary. Sees __sk_buff. Return 0 to drop, 1 to allow. Operates on IPs/ports, not hostnames.

  2. BPF_PROG_TYPE_CGROUP_SOCK_ADDR — hooks connect4 / connect6 / bind / sendmsg syscalls. Can rewrite the destination IP + port before the kernel proceeds with the connection. Paired with CGROUP_SKB to compose name-aware policy (see patterns/dns-proxy-for-hostname-filtering).

  3. eBPF maps — the policy interchange contract between userspace (writes policy) and kernel programs (read policy per-event). Typical shapes: BPF_MAP_TYPE_HASH (blocked-IP set), BPF_MAP_TYPE_LRU_HASH (recently-blocked DNS TXIDs), BPF_MAP_TYPE_ARRAY (simple counters).

Why cGroup scope rather than host / interface scope

  • Co-tenanted hosts. Stateful or customer-serving processes on the same box legitimately need network egress that a newly-deployed deploy script or maintenance task must be blocked from. Host-wide iptables / XDP over-blocks.
  • Same network namespace. Container-level namespace isolation would require containerising the target process — a huge commitment just for egress filtering. cGroup attachment is lighter.
  • Per-connection attribution. Because the eBPF programs run in the context of the originating process, helpers like bpf_get_current_pid_tgid() work — you can log which process triggered a drop, not just that something did.

Why stateful-host scoping rather than "bake a dedicated VM"

GitHub's post names the force: "Blocking github.com entirely would impact their ability to handle production requests" — the hosts are stateful and serve customer traffic during rolling deploys, drains, restarts. The alternatives (spin a fresh VM for the deploy script, dockerize the deploy, move to a separate deploy host) all:

  • Add orchestration + bootstrapping cost for a script that runs for seconds.
  • Change the rootfs / tooling model that the deploy expects.
  • Don't give per-process attribution back to the owning team.

cGroup-scoping preserves the existing deploy-script model and just layers policy on top.

Bonus: resource limits come free

A cGroup can enforce CPU + memory + PID caps on the contained process. GitHub cites this as a second-order benefit: a runaway deploy script can't starve the customer-serving processes on the same host. "Use the cGroups to enforce CPU and memory limits on deploy scripts, preventing runaway resource usage from impacting workloads."

Relationship to other wiki patterns

  • patterns/dns-proxy-for-hostname-filtering — the specific mechanism that elevates a cGroup-scoped firewall from IP-based to hostname-based policy.
  • patterns/two-stage-evaluation — the cGroup eBPF programs (kernel, cheap per-packet) + userspace DNS proxy / policy engine (rich per-query evaluation) compose as a two-stage pipeline on the same kernel primitive as Datadog's FIM.
  • patterns/approver-discarder-filter — Datadog-FIM-specific shape of the eBPF-map policy payload; a cGroup-scoped firewall could use the same shape.
  • patterns/shared-kernel-resource-coordination — multi- vendor eBPF coexistence on shared kernel resources; any cGroup-scoped firewall must coordinate with other eBPF tools on the host (e.g. Cilium CNI, Datadog Workload Protection) on TC priorities and program ordering.
  • Alternative: concepts/egress-sni-filtering at the VPC / middlebox layer (AWS Network Firewall) filters all traffic out of a subnet based on TLS ClientHello SNI, without host involvement. Trade-offs: no per-process attribution, doesn't require touching hosts, works across heterogeneous workloads. cGroup-scoped firewall is narrower + gives better forensic data; SNI filtering is simpler ops and infra-uniform.

Caveats

  • Verifier + kernel-version constraints. Same operational costs as any other eBPF production deployment — see concepts/ebpf-verifier and sources/2026-01-07-datadog-hardening-ebpf-for-runtime-security for the full pitfall surface.
  • Policy is only as good as cGroup discipline. If a deploy script spawns a sibling process outside the cGroup, that sibling isn't policed. The orchestrator must place all deploy-related processes in the cGroup (via cgroup.procs writes on spawn, or by launching through a cGroup-aware wrapper).
  • IP-based policy can't keep up. Hostname-based policy needs patterns/dns-proxy-for-hostname-filtering or an SNI-inspection layer; a pure CGROUP_SKB-only implementation devolves into an ever-out-of-date IP blocklist.
  • Fail-open vs fail-closed when the userspace policy engine (DNS proxy, rule compiler) crashes needs explicit design. Not detailed in the GitHub post.
  • Not a silver bullet for circular dependencies. GitHub closes the post with "Are there ways for circular dependencies to still trip things up? You bet" — the firewall catches one class of circular dependency (outbound-network-to-self), not all.

Seen in

Last updated · 200 distilled / 1,178 read