Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Keep the build context small — the Dockerfile only needs sources + Cargo
# manifests. Everything below is either rebuilt inside the image or irrelevant.
target/
.git/
*.md
docs/
deploy/
.github/
.vscode/
.idea/
19 changes: 14 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ its first tagged release.
## [Unreleased]

### Added
- **Podman deployment notes** — `docs/deployment.md` documents running the eBPF
egress guard under Podman (cgroup-v2-native, daemonless): rootful/privileged
`podman run` with `--cgroupns host`, the pod-sidecar mapping (incl. `podman play
kube`), and why rootless Podman only fits the cooperative proxy layer. Marked as
untested-against-a-live-host, like the Kubernetes examples.
- **Podman deployment notes (verified)** — `docs/deployment.md` documents running
the eBPF egress guard under Podman (cgroup-v2-native, daemonless), **verified on
Lima** (Ubuntu 24.04, kernel 6.8, Podman 4.9.3): rootful/privileged self-guard
(default cgroupns) and sidecar (`--cgroupns host` + the target's dedicated cgroup
path) both enforce the allowlist in the kernel with host egress intact. Documents
the anti-pattern proven along the way — `--cgroupns host` on a `/sys/fs/cgroup`
attach cuts the *host's* egress — and why rootless Podman only fits the
cooperative proxy layer.
- **`AGENTS.md` + `.github/skills/`** — a vendor-neutral orientation guide for
coding agents and new contributors (build/test, crate map, working rules,
deferring to CLAUDE.md as the binding authority), plus step-by-step task
Expand Down Expand Up @@ -64,5 +67,11 @@ its first tagged release.
- README: dependency-pin table, container/Helm quickstart.

### Fixed
- **Container builds now work on Podman** (surfaced verifying the Podman notes):
the `deploy/Dockerfile` and `deploy/proxy/Dockerfile` base images and the
compose `curl` image are now fully-qualified (`docker.io/library/...`), so they
resolve under Podman's stricter short-name policy (Docker is unaffected); and a
`.dockerignore` excludes `target/` etc. so the build context is no longer the
whole 6.6 GB tree.
- `pasu-ebpf` was missing a `license` field; `pasu-egress` was missing the
`io-util`/`sync` tokio features (surfaced by a clean build).
4 changes: 2 additions & 2 deletions deploy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# a cgroup v2 mount, and a DEDICATED cgroup to attach to).

########## builder ##########
FROM rust:1-bookworm AS builder
FROM docker.io/library/rust:1-bookworm AS builder

# eBPF build toolchain: nightly + rust-src (build-std for the bpf target).
RUN rustup toolchain install nightly --profile minimal --component rust-src \
Expand All @@ -35,7 +35,7 @@ COPY . .
RUN cargo build -p pasu-egress -p pasu-daemon -p pasu-cli --release

########## runtime ##########
FROM debian:bookworm-slim AS runtime
FROM docker.io/library/debian:bookworm-slim AS runtime

# ca-certificates for DNS/TLS metadata; curl + iproute2 + socat make the image
# useful for the demo / admin CLI. Drop them for a smaller production image.
Expand Down
2 changes: 1 addition & 1 deletion deploy/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# name and adjust the attach path accordingly.
services:
agent:
image: curlimages/curl:latest
image: docker.io/curlimages/curl:latest
# a stand-in agent: keeps trying an allowed and a blocked destination
command:
- sh
Expand Down
4 changes: 2 additions & 2 deletions deploy/proxy/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
# docker build -f deploy/proxy/Dockerfile -t pasu-proxy:latest .

########## builder ##########
FROM rust:1-bookworm AS builder
FROM docker.io/library/rust:1-bookworm AS builder
WORKDIR /src
COPY . .
# Portable/stable build — the proxy crate needs no eBPF toolchain.
RUN cargo build --release -p pasu-proxy --bin pasu-proxy

########## runtime ##########
FROM debian:bookworm-slim
FROM docker.io/library/debian:bookworm-slim
# CA roots for TLS to the real provider (https upstream).
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
Expand Down
51 changes: 36 additions & 15 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,19 @@ cgroup layout.
The "one rule" is runtime-agnostic: pasu-egress needs a **cgroup v2** node and the
capability to attach a `cgroup_skb` program to it — not Docker specifically.
Podman is **cgroup-v2-native and daemonless**, so the requirements above map
cleanly. Two paths, mirroring the Docker ones:
cleanly. Run it **rootful** (`sudo podman`); see the rootless note below.

**Rootful, privileged** (the direct analog of §2/§3) — build with `podman build`
(same [`deploy/Dockerfile`](../deploy/Dockerfile)), then:
Podman's default seccomp profile blocks `bpf()` just like Docker's, so
`--privileged` is the easy path (or grant `CAP_BPF` + `CAP_NET_ADMIN`
(+ `CAP_PERFMON`) with a profile that allows `bpf`).

**Self-guard (one container)** — attaches to the container's own cgroup, so use
Podman's **default (private) cgroupns**; `/sys/fs/cgroup` is then the container's
own cgroup. Build with `podman build` (same [`deploy/Dockerfile`](../deploy/Dockerfile)),
then:

```bash
sudo podman run --rm --privileged --cgroupns host --entrypoint /bin/sh \
sudo podman run --rm --privileged --entrypoint /bin/sh \
pasu-egress:latest -c '
pasu-egress --cgroup-path /sys/fs/cgroup --allow 1.1.1.1 &
sleep 3
Expand All @@ -115,15 +121,28 @@ sudo podman run --rm --privileged --cgroupns host --entrypoint /bin/sh \
'
```

The same two gotchas from §4 apply: pass **`--cgroupns host`** (Podman also gives a
private cgroupns by default), and Podman's default seccomp profile blocks `bpf()`
too — `--privileged` clears it, or grant `CAP_BPF` + `CAP_NET_ADMIN` (+ `CAP_PERFMON`)
with a seccomp profile that allows `bpf`.
> ⚠️ **Do _not_ add `--cgroupns host` to the self-guard command.** With the host
> cgroupns, `/sys/fs/cgroup` is the **host root cgroup**, and default-deny there
> cuts the whole host's egress (verified: the host itself lost egress to
> non-allowed IPs). `--cgroupns host` is only for the sidecar case below, and
> then you attach to a **dedicated** cgroup path, never `/sys/fs/cgroup`.

**Sidecar (guard a separate container)** — the guard needs `--cgroupns host` to
reach the target's cgroup, and attaches to that **specific** cgroup path (from
`podman inspect`), which scopes enforcement to the target and leaves the host
untouched:

```bash
sudo podman run -d --name agent ... # your agent workload
AGCG=$(sudo podman inspect agent --format '{{.State.CgroupPath}}')
sudo podman run -d --privileged --cgroupns host --entrypoint pasu-egress \
pasu-egress:latest --cgroup-path "/sys/fs/cgroup$AGCG" --allow 1.1.1.1
# agent now reaches 1.1.1.1 but not 1.0.0.1; the host's own egress is unaffected.
```

**Pod sidecar** — a Podman pod shares a cgroup across its containers, so it maps
directly onto the Kubernetes sidecar model (§4). The [k8s manifests](../deploy/k8s/)
also run under Podman via `podman play kube`: the privileged pasu-egress container
attaches to the shared pod cgroup and guards the agent container.
A Podman **pod** shares a cgroup across its containers, so `podman play kube` on
the [k8s manifests](../deploy/k8s/) maps onto the same sidecar model (§4) — the
privileged pasu-egress container attaches to the pod's cgroup slice.

> ⚠️ **Rootless Podman is the hard case.** A rootless container runs in a user
> namespace with a delegated cgroup subtree, and attaching a cgroup-BPF program
Expand All @@ -132,9 +151,11 @@ attaches to the shared pod cgroup and guards the agent container.
> userspace library/binary, runs fine rootless — `podman run` it and point the
> agent's `base_url` at it.)

> These Podman commands follow the same shape as the verified Docker paths but
> have **not been run against a live Podman host** — treat them as a starting
> point, like the Kubernetes examples above.
> **Verified** on Lima (Ubuntu 24.04, kernel 6.8, cgroup v2, **Podman 4.9.3**,
> arm64): the self-guard and sidecar commands above both enforce the allowlist in
> the kernel while leaving host egress intact; `--cgroupns host` on a
> `/sys/fs/cgroup` attach cuts the host, as warned. `podman play kube` is inferred
> from the shared-cgroup model, not separately run.

### Two gotchas we hit validating this (so you don't have to)

Expand Down
Loading