GoSuda

Spouštění Dockeru, docker build a docker compose bez oprávnění root nebo jmenných prostorů uživatelů

By Lemon Mint views ...

Rychlé řešení: Bezpečný neprivilegovaný Docker ve třech krocích

Pokud pracujete uvnitř zabezpečeného Linuxového kontejneru, CI runneru, Kubernetes Podu nebo sandboxu AI agenta, kde jsou blokovány root, sudo, user namespaces (unshare(CLONE_NEWUSER)), mount(2) a /dev/fuse, je gvx (rungv) bezpečné řešení založené na principu defense-in-depth pro spuštění plnohodnotného enginu docker:dind v uživatelském prostoru.

Proč je gvx bezpečný již svým návrhem (nejde o obejití eskalace oprávnění): Na rozdíl od připojení hostitelského /var/run/docker.sock nebo spuštění docker run --privileged (které poskytují řízení jádra hostitele ekvivalentní rootovi) gvx neobchází ani neoslabuje žádnou bezpečnostní politiku hostitele (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor ani omezení non-root UID). Místo toho rungv uzamkne vlastní hostitelský proces (PR_SET_NO_NEW_PRIVS + přísný BPF seccomp allowlist) a spouští dockerd uvnitř jádra v uživatelském prostoru gVisor (Sentry) od Googlu, které je chráněno proti chybám v práci s pamětí. Všechny UID 0 schopnosti hosta, připojení overlayfs (ext4lite) a pravidla iptables zůstávají přísně izolovány uvnitř procesu sandboxu.

 1# 1. Nainstalujte rungv v uživatelském prostoru (předkompilované vydání s attestací SLSA nebo go install)
 2# Možnost A: Ověřte a rozbalte předkompilovanou binárku s attestací SLSA z GitHub Releases
 3gh attestation verify rungv-v0.2.0-linux-amd64.tar.gz --repo gfire-sigs/gvx \
 4  --signer-workflow gfire-sigs/gvx/.github/workflows/release.yml \
 5  --source-ref refs/tags/v0.2.0 --deny-self-hosted-runners
 6tar -xzf rungv-v0.2.0-linux-amd64.tar.gz && install -m 0755 rungv-v0.2.0-linux-amd64/rungv ~/.local/bin/rungv
 7
 8# Možnost B: Nebo nainstalujte přímo ze zdrojového kódu pomocí Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Spusťte docker:dind uvnitř gvx se svazkem ext4lite tvořeným jediným souborem
12#    Přesměrovává ~/.gvx/docker.sock (režim 0600) na port 2375 guesta
13mkdir -p ~/.gvx
14~/.local/bin/rungv run \
15  -p unix:~/.gvx/docker.sock:2375 \
16  -p 127.0.0.1:9000-9019:9000-9019 \
17  -v gvx-docker:/var/lib/docker \
18  -e DOCKER_TLS_CERTDIR= \
19  docker:dind --tls=false &
20
21# 3. Nasměrujte standardní Docker CLI a Docker Compose na socket v uživatelském prostoru
22export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
23until docker info >/dev/null 2>&1; do sleep 0.5; done
24docker info --format '{{.Driver}} {{.OperatingSystem}}'
25# Výstup: overlayfs Alpine Linux ... (v kontejneru)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Proč standardní nástroje pro běh bez roota selhávají v omezených sandboxech

Když inženýr nebo autonomní kódovací agent potřebuje spustit docker build, docker run nebo docker compose uvnitř zpevněného kontejneru nebo multi-tenant runneru, standardní obcházení selhávají na pěti odlišných jaderných bariérách.

1. Žádný socket démona Dockeru na hostiteli

Spuštění docker ps nebo docker build okamžitě selže, pokud není hostitelský /var/run/docker.sock připojen do prostředí:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?

2. PR_SET_NO_NEW_PRIVS blokuje sudo a setuid pomocné programy

Zpevněné kontejnery (securityContext.allowPrivilegeEscalation: false v Kubernetes, --security-opt=no-new-privileges v Dockeru, nebo sandboxy exebox / bwrap) nastavují příznak Linuxu PR_SET_NO_NEW_PRIVS na stromu procesů. Pokus o eskalaci přes sudo nebo setuid binárky (newuidmap, newgidmap, fusermount3) selže:

1sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
2sudo: effective uid is not 0, is /usr/bin/sudo on a file system with the 'nosuid' option set or an NFS file system without root privileges?

3. Politiky seccomp a sysctl blokují unshare(CLONE_NEWUSER)

Nástroje propagované jako „rootless“ (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) se stále spoléhají na Linuxové user namespaces (CLONE_NEWUSER). Selhávají vždy, když:

  • Výchozí seccomp profil vnějšího kontejneru blokuje unshare(2) nebo clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ zapíná kernel.apparmor_restrict_unprivileged_userns = 1.
  • Uzly Debianu nebo RHEL nastavují kernel.unprivileged_userns_clone = 0 nebo user.max_user_namespaces = 0.
  • newuidmap nemůže zapisovat do /proc/self/uid_map, protože je aktivní PR_SET_NO_NEW_PRIVS.

Výsledné chyby vypadají takto:

1unshare: unshare failed: Operation not permitted
2cannot clone: Operation not permitted
3Error: cannot re-exec process to join the existing user namespace
4newuidmap: write to uid_map failed: Operation not permitted
5RootlessKit: parent: failed to start the child: fork/exec /proc/self/exe: operation not permitted

4. mount(2) je blokován bez CAP_SYS_ADMIN na hostiteli

I když prostředí povoluje CLONE_NEWUSER, připojení čerstvého /proc uvnitř vnořeného mount namespace selže, pokud vnější kontejner maskuje podstromy /proc (/proc/acpi, /proc/kcore, /proc/keys) nebo má v seccomp zakázaný mount(2):

1mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
2OCI runtime create failed: runc create failed: unable to start container process: error during container init: error mounting "proc" to rootfs at "/proc": mount proc:/proc (via /proc/self/fd/6), flags: 0xe: operation not permitted
3bwrap: Creating new namespace failed: Operation not permitted

5. Chybějící /dev/fuse rozbíjí fuse-overlayfs

Rootless enginy kontejnerů, které nedokážou připojit jaderný overlayfs, přecházejí na fuse-overlayfs. V minimálních kontejnerech nebo Kubernetes podech bez předání zařízení /dev/fuse fuse-overlayfs okamžitě selže:

1fuse: device not found, try 'modprobe fuse' first
2fusermount3: failed to open /dev/fuse: No such file or directory
3error initializing graphdriver: driver not supported: fuse-overlayfs

Jak gvx spouští docker:dind zcela v uživatelském prostoru

gvx obchází všech pět jaderných bariér, protože nežádá hostitelské Linuxové jádro o vytváření namespaces, připojování souborových systémů ani konfiguraci síťových rozhraní. Místo toho rungv spouští interní Linuxové jádro v uživatelském prostoru (gVisor Sentry) a síťový zásobník v uživatelském prostoru (netstack) uvnitř jediného běžného neprivilegovaného procesu.

Spuštění neprivilegovaného Docker-in-Docker pomocí gvx

1. Namespaces a tabulka připojení v paměti (VFS2)

Když dockerd, containerd a runc běží uvnitř rungv, systrap zachytává jejich systémová volání a směruje je do procesního jádra Sentry místo jádra hostitele:

  • unshare(2), clone(2), pivot_root(2), chroot(2) a mount(2) pracují nad hierarchií úloh Sentry v paměti a tabulkou připojení VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), zatímco hostitelský proces rungv běží pod vaším neprivilegovaným UID na hostiteli.

2. Nativní overlayfs na svazcích ext4lite tvořených jediným souborem

Když předáte -v gvx-docker:/var/lib/docker (nebo --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx připojí svůj vestavěný souborový systém ext4lite na /var/lib/docker.

Protože ext4lite implementuje souborový systém ext4 s žurnálem JBD2 uvnitř jediného souboru na hostiteli (gvx-docker.ext4) s podporou rozšiřujících atributů trusted.overlay.*, znakových whiteout zařízení (0:0), d_type a RENAME_EXCHANGE, dockerd uvnitř guesta používá svůj nativní storage driver overlayfs. Vícestupňové operace docker build sdílejí vrstvy Copy-on-Write bez /dev/fuse a bez explozivního růstu využití disku, který způsobuje záložní driver vfs od Dockeru.

3. Most docker0 v uživatelském prostoru a iptables-legacy

Když se docker:dind spustí uvnitř gvx:

  • dockerd v guestovi vytváří most docker0 (172.17.0.1/16), připojuje virtuální páry veth pro vnořené kontejnery a programuje řetězce NAT (DOCKER, POSTROUTING) a FILTER s pravidly addrtype a conntrack uvnitř síťového zásobníku netstack v uživatelském prostoru gvx.
  • Předání -p unix:~/.gvx/docker.sock:2375 naváže hostitelský Unix doménový socket (režim 0600) a přesměruje spojení přímo na port 2375 démona dockerd uvnitř sandboxu.

Srovnání s existujícími nástroji pro kontejnery

Schopnost / omezeníStandardní Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (neprivilegovaný)gvx (rungv + docker:dind)
Vyžadován root nebo sudo na hostiteliAnoNeNeNe
Funguje s PR_SET_NO_NEW_PRIVS=1NeNe (newuidmap selže)ČástečněAno
Funguje, když je unshare(CLONE_NEWUSER) blokováno (EPERM)N/ANeBuildah: Ne / Kaniko: AnoAno
Funguje bez /dev/fuseAno (používá jaderný overlay2)Pouze přes pomalou kopii vfsAnoAno (overlayfs na ext4lite)
Podporuje docker build (BuildKit)AnoAno (pokud je userns povolen)Pouze sestavení image (bez runtime)Ano
Podporuje docker run a docker compose upAnoAno (pokud je userns povolen)NeAno
Běží na Windows a macOS bez virtuálního stroje s LinuxemNe (vyžaduje WSL2 / VM)Ne (vyžaduje VM s Linuxem)NeAno (whpx, hvf, systrap)

Přesné požadavky na jádro hostitele Linux

gvx nevyžaduje žádné zvýšené schopnosti (CapEff: 0000000000000000) ani žádné user namespaces. Na hostiteli Linux se rungv spoléhá pouze na tři standardní neprivilegovaná primitiva jádra:

  1. ptrace(PTRACE_ATTACH) na vlastním podřízeném vlákně jednou během inicializace: systrap při startu spustí lokální pomocné stub vlákno, připojí se k němu, aby inicializoval sdílený kontext paměti, a odpojí se. Pokud hostitel nastaví kernel.yama.ptrace_scope = 3 (což zakáže veškeré ptrace v celém systému) nebo zablokuje ptrace ve vnějším seccomp filtru, inicializace systrap nemůže být dokončena. (Při yama.ptrace_scope = 1, což je výchozí nastavení na Ubuntu a Debianu, gvx uspěje, protože sledování z rodiče na potomka je povoleno.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Používá se k zachycení systémových volání guesta z pomocných stub vláken kontejneru. Jak je popsáno v části 3 této série, gvx funguje jak tehdy, když je k dispozici SECCOMP_FILTER_FLAG_NEW_LISTENER, tak tehdy, když vrací EBUSY uvnitř vnořeného sandboxu.
  3. mmap(..., PROT_EXEC, ...): Používá se k mapování segmentů ELF guesta a sdíleného trampolinu systrap v paměti.

Příklad od začátku do konce: sestavení a testování aplikace s více kontejnery bez roota

Následující skript spustí kompletní docker build a integrační test se dvěma kontejnery (redis + kontejner aplikace) uvnitř neprivilegované uživatelské relace:

 1mkdir -p ~/.gvx
 2rungv run \
 3  -p unix:~/.gvx/docker.sock:2375 \
 4  -v gvx-docker:/var/lib/docker \
 5  -e DOCKER_TLS_CERTDIR= \
 6  docker:dind --tls=false &
 7DIND_PID=$!
 8
 9export DOCKER_HOST="unix://$HOME/.gvx/docker.sock"
10until docker info >/dev/null 2>&1; do sleep 0.5; done
11
12mkdir -p /tmp/demo-app && cd /tmp/demo-app
13cat <<'EOF' > Dockerfile
14FROM alpine:3.20
15RUN apk add --no-cache redis
16CMD ["redis-cli", "-h", "cache", "ping"]
17EOF
18
19# Sestavte image pomocí overlayfs na ext4lite
20docker build -t demo-ping:latest .
21
22# Vytvořte síť typu bridge, spusťte Redis a ověřte spojení mezi kontejnery
23docker network create demo-net
24docker run -d --name cache --network demo-net redis:7-alpine
25docker run --rm --network demo-net demo-ping:latest
26# Výstup: PONG
27
28# Uklidněte pracovní kontejnery a zastavte sandbox
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Další čtení v této sérii

  1. gvx: Spouštění Linuxových kontejnerů, Dockeru a Kubernetes v jediném neprivilegovaném procesu: Architektonický přehled gvx, jeho multiplatformních výpočetních enginů (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) a netstack.
  2. Spouštění Dockeru, docker build a docker compose bez roota nebo user namespaces (tento článek)
  3. Spouštění Docker-in-Docker uvnitř gVisor a vnořených sandboxů pomocí gvx: Jak gvx řeší modprobe, iptables-nft, overlay2 na gofer a vnořený SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) uvnitř gVisor (runsc).
  4. Rychlá izolovaná prostředí Dockeru pro RL rollouty AI agentů pomocí gvx: Škálování paralelních prostředí SWE-bench a RL rolloutů se 100milisekundovými resety svazků .ext4 typu copy-on-write.