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

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
gvxbezpečný již svým návrhem (nejde o obejití eskalace oprávnění): Na rozdíl od připojení hostitelského/var/run/docker.socknebo spuštěnídocker run --privileged(které poskytují řízení jádra hostitele ekvivalentní rootovi)gvxneobchází ani neoslabuje žádnou bezpečnostní politiku hostitele (PR_SET_NO_NEW_PRIVS,seccomp, AppArmor ani omezení non-root UID). Místo tohorungvuzamkne vlastní hostitelský proces (PR_SET_NO_NEW_PRIVS+ přísný BPFseccompallowlist) a spouštídockerduvnitř 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šechnyUID 0schopnosti hosta, připojeníoverlayfs(ext4lite) a pravidlaiptableszů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)neboclone(CLONE_NEWUSER). - Ubuntu 24.04+ zapíná
kernel.apparmor_restrict_unprivileged_userns = 1. - Uzly Debianu nebo RHEL nastavují
kernel.unprivileged_userns_clone = 0nebouser.max_user_namespaces = 0. newuidmapnemůž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.

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)amount(2)pracují nad hierarchií úloh Sentry v paměti a tabulkou připojeníVFS2(procfs,sysfs,tmpfs,devpts,cgroupfsv2), zatímco hostitelský procesrungvběží 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:
dockerdv guestovi vytváří mostdocker0(172.17.0.1/16), připojuje virtuální páryvethpro vnořené kontejnery a programuje řetězceNAT(DOCKER,POSTROUTING) aFILTERs pravidlyaddrtypeaconntrackuvnitř síťového zásobníkunetstackv uživatelském prostorugvx.- Předání
-p unix:~/.gvx/docker.sock:2375naváže hostitelský Unix doménový socket (režim0600) a přesměruje spojení přímo na port2375démonadockerduvnitř sandboxu.
Srovnání s existujícími nástroji pro kontejnery
| Schopnost / omezení | Standardní Docker (dockerd) | Rootless Docker / Podman | Kaniko / Buildah (neprivilegovaný) | gvx (rungv + docker:dind) |
|---|---|---|---|---|
Vyžadován root nebo sudo na hostiteli | Ano | Ne | Ne | Ne |
Funguje s PR_SET_NO_NEW_PRIVS=1 | Ne | Ne (newuidmap selže) | Částečně | Ano |
Funguje, když je unshare(CLONE_NEWUSER) blokováno (EPERM) | N/A | Ne | Buildah: Ne / Kaniko: Ano | Ano |
Funguje bez /dev/fuse | Ano (používá jaderný overlay2) | Pouze přes pomalou kopii vfs | Ano | Ano (overlayfs na ext4lite) |
Podporuje docker build (BuildKit) | Ano | Ano (pokud je userns povolen) | Pouze sestavení image (bez runtime) | Ano |
Podporuje docker run a docker compose up | Ano | Ano (pokud je userns povolen) | Ne | Ano |
| Běží na Windows a macOS bez virtuálního stroje s Linuxem | Ne (vyžaduje WSL2 / VM) | Ne (vyžaduje VM s Linuxem) | Ne | Ano (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:
ptrace(PTRACE_ATTACH)na vlastním podřízeném vlákně jednou během inicializace:systrappř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éptracev celém systému) nebo zablokujeptraceve vnějším seccomp filtru, inicializacesystrapnemůže být dokončena. (Přiyama.ptrace_scope = 1, což je výchozí nastavení na Ubuntu a Debianu,gvxuspěje, protože sledování z rodiče na potomka je povoleno.)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,gvxfunguje jak tehdy, když je k dispoziciSECCOMP_FILTER_FLAG_NEW_LISTENER, tak tehdy, když vracíEBUSYuvnitř vnořeného sandboxu.mmap(..., PROT_EXEC, ...): Používá se k mapování segmentů ELF guesta a sdíleného trampolinusystrapv 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
- 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,hostfsv2) anetstack. - Spouštění Dockeru, docker build a docker compose bez roota nebo user namespaces (tento článek)
- Spouštění Docker-in-Docker uvnitř gVisor a vnořených sandboxů pomocí gvx: Jak
gvxřešímodprobe,iptables-nft,overlay2nagofera vnořenýSECCOMP_FILTER_FLAG_NEW_LISTENER(EBUSY) uvnitřgVisor(runsc). - Rychlá izolovaná prostředí Dockeru pro RL rollouty AI agentů pomocí gvx: Škálování paralelních prostředí
SWE-bencha RL rolloutů se 100milisekundovými resety svazků.ext4typu copy-on-write.