GoSuda

Rularea Docker, docker build și docker compose fără root sau user namespaces

By Lemon Mint views ...

Soluție rapidă: Docker neprivilegiat securizat în trei pași

Dacă lucrați într-un container Linux restricționat, un runner CI, un Kubernetes Pod sau un mediu sandbox pentru agenți de programare bazați pe AI (cum ar fi un mediu de calcul cloud pentru agenți) în care root, sudo, spațiile de nume ale utilizatorilor (unshare(CLONE_NEWUSER)), mount(2) și /dev/fuse sunt blocate, gvx (rungv) reprezintă soluția sigură, cu efort redus și eficientă din punct de vedere al tokenilor pentru a rula un motor docker:dind complet în spațiul utilizatorului.

Soluție cu efort redus și eficientă din punct de vedere al tokenilor pentru agenții de programare bazați pe AI: Atunci când un agent de programare autonom întâlnește eroarea Cannot connect to the Docker daemon, încercarea de a instala și de a modifica docker.io, podman sau rootlesskit într-un container restricționat consumă, de regulă, între 15 și 30 de runde de apeluri de instrumente și zeci de mii de tokeni de context prin erori succesive provenite de la systemctl, ulimit, iptables, unshare și runc, fără a reuși sau ajungând să rescrie manual Dockerfile-urile. Cu gvx (rungv), agentul execută un singur bootstrap determinist în 3 pași, într-o singură rundă, și rulează imediat comenzile standard docker build, docker run și docker compose fără modificări.

De ce gvx este sigur prin design (și nu un mecanism de escaladare a privilegiilor): Spre deosebire de montarea socket-ului gazdei /var/run/docker.sock sau de rularea cu docker run --privileged (care acordă un control echivalent cu cel de root asupra nucleului gazdei), gvx nu ocolește și nu slăbește nicio politică de securitate a gazdei (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor sau limitările UID-urilor non-root). Dimpotrivă, rungv își blochează propriul proces gazdă (PR_SET_NO_NEW_PRIVS + o listă de permisiuni BPF seccomp strict) și rulează dockerd în interiorul nucleului în spațiul utilizatorului gVisor (Sentry), dezvoltat de Google, cu siguranță a memoriei. Toate capabilitățile UID 0 din interiorul sistemului invitat, montările overlayfs (ext4lite) și regulile iptables rămân strict izolate în interiorul procesului sandbox.

 1# 1. Instalați rungv în spațiul utilizatorului (binar precompilat cu atestare SLSA sau go install)
 2# Opțiunea A: Verificați și extrageți binarul precompilat cu atestare SLSA din 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# Opțiunea B: Sau instalați direct din sursă, cu Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Porniți docker:dind în interiorul gvx, cu un volum ext4lite dintr-un singur fișier
12#    Redirecționează ~/.gvx/docker.sock (modul 0600) către portul 2375 al sistemului invitat
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. Configurați Docker CLI standard și Docker Compose să utilizeze socket-ul din spațiul utilizatorului
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# Rezultat: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

De ce instrumentele standard fără privilegii root eșuează în sandbox-uri restricționate

Atunci când un inginer sau un agent de programare autonom trebuie să ruleze docker build, docker run sau docker compose într-un container întărit sau într-un runner multi-tenant, soluțiile alternative standard eșuează la cinci bariere distincte ale nucleului.

1. Lipsa socket-ului daemonului Docker al gazdei (systemctl și service docker start eșuează, de asemenea)

Rularea docker ps sau docker build eșuează imediat atunci când socket-ul /var/run/docker.sock al gazdei nu este montat în mediu, iar încercarea de a porni docker.io prin systemctl sau service docker start eșuează deoarece PID 1 nu este systemd, iar /etc/init.d/docker atinge plafonul strict ulimit -n al containerului:

1Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
2ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
3System has not been booted with systemd as init system (PID 1). Can't operate.
4Failed to connect to bus: Host is down
5/etc/init.d/docker: 62: ulimit: error setting limit (Operation not permitted)

2. PR_SET_NO_NEW_PRIVS blochează sudo și utilitarele setuid

Containerele întărite (securityContext.allowPrivilegeEscalation: false în Kubernetes, --security-opt=no-new-privileges în Docker sau sandbox-urile exebox / bwrap) setează bitul Linux PR_SET_NO_NEW_PRIVS pe arborele de procese. Încercarea de escaladare prin sudo sau binarele setuid (newuidmap, newgidmap, fusermount3) eșuează:

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. Politicile seccomp și sysctl blochează unshare(CLONE_NEWUSER)

Instrumentele promovate drept „rootless” (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap) se bazează totuși pe spațiile de nume ale utilizatorilor ale Linux (CLONE_NEWUSER). Ele eșuează ori de câte ori:

  • Profilul seccomp implicit al containerului exterior blochează unshare(2) sau clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ activează kernel.apparmor_restrict_unprivileged_userns = 1.
  • Nodurile Debian sau RHEL setează kernel.unprivileged_userns_clone = 0 sau user.max_user_namespaces = 0.
  • newuidmap nu poate scrie în /proc/self/uid_map deoarece PR_SET_NO_NEW_PRIVS este activ.

Erorile rezultate arată astfel:

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) și pivot_root(2) sunt blocate în timpul docker build fără CAP_SYS_ADMIN pe gazdă

Chiar dacă un mediu permite CLONE_NEWUSER sau dockerd este forțat să pornească cu --iptables=false --bridge=none --storage-driver=vfs, rularea docker build eșuează la prima instrucțiune RUN, deoarece montarea unui /proc nou sau apelul pivot_root(2) într-un spațiu de nume de montare imbricat este blocat:

1ERROR: failed to solve: process "/bin/sh -c ..." did not complete successfully:
2mount("/proc", "/proc", "proc", MS_NOSUID|MS_NODEV|MS_NOEXEC, NULL): Operation not permitted
3OCI 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
4OCI runtime create failed: runc create failed: unable to start container process: error during container init: error jailing process inside rootfs: pivot_root .: invalid argument
5bwrap: Creating new namespace failed: Operation not permitted

5. Lipsa /dev/fuse afectează fuse-overlayfs

Motoarele de containere fără privilegii root care nu pot monta overlayfs de nucleu revin la fuse-overlayfs. În containerele minimale sau în pod-urile Kubernetes fără transmiterea dispozitivului /dev/fuse, fuse-overlayfs eșuează imediat:

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

Cum rulează gvx docker:dind complet în spațiul utilizatorului

gvx ocolește toate cele cinci bariere ale nucleului deoarece nu solicită nucleului Linux al gazdei să creeze spații de nume, să monteze sisteme de fișiere sau să configureze interfețe de rețea. În schimb, rungv rulează un nucleu Linux intern, în spațiul utilizatorului (gVisor Sentry), și o stivă de rețea în spațiul utilizatorului (netstack) în interiorul unui singur proces obișnuit, fără privilegii.

Rularea Docker-in-Docker fără privilegii cu gvx

1. Spații de nume și tabelă de montare în memorie (VFS2)

Atunci când dockerd, containerd și runc se execută în interiorul rungv, systrap interceptează apelurile lor de sistem și le direcționează către nucleul Sentry din proces, nu către nucleul gazdei:

  • unshare(2), clone(2), pivot_root(2), chroot(2) și mount(2) operează asupra ierarhiei de sarcini în memorie a Sentry și asupra tabelei de montare VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), în timp ce procesul gazdă rungv rulează sub UID-ul dumneavoastră neprivilegiat de pe gazdă.

2. overlayfs nativ pe volume ext4lite dintr-un singur fișier

Atunci când transmiteți -v gvx-docker:/var/lib/docker (sau --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx montează sistemul de fișiere ext4lite integrat la /var/lib/docker.

Deoarece ext4lite implementează un sistem de fișiere ext4 cu jurnal JBD2, stocat într-un singur fișier al gazdei (gvx-docker.ext4), cu suport pentru atributele extinse trusted.overlay.*, dispozitive whiteout de tip caracter (0:0), d_type și RENAME_EXCHANGE, dockerd utilizează driverul de stocare nativ overlayfs în interiorul sistemului invitat. Operațiile docker build în mai multe etape partajează straturi Copy-on-Write fără /dev/fuse și fără explozia spațiului pe disc specifică driverului de rezervă vfs al Docker.

3. Puntea docker0 în spațiul utilizatorului și iptables-legacy

Atunci când docker:dind pornește în gvx:

  • dockerd din sistemul invitat creează puntea docker0 (172.17.0.1/16), atașează perechi virtuale veth pentru containerele imbricate și programează lanțurile NAT (DOCKER, POSTROUTING) și FILTER cu potriviri addrtype și conntrack în interiorul netstack al gvx, care rulează în spațiul utilizatorului.
  • Transmiterea -p unix:~/.gvx/docker.sock:2375 leagă un socket Unix de domeniu al gazdei (modul 0600) și redirecționează conexiunile direct către portul 2375 al dockerd din interiorul sandbox-ului.

Comparație cu instrumentele de containerizare existente

Capacitate / RestricțieDocker standard (dockerd)Docker Rootless / PodmanKaniko / Buildah (neprivilegiat)gvx (rungv + docker:dind)
Necesită root sau sudo pe gazdăDaNuNuNu
Funcționează cu PR_SET_NO_NEW_PRIVS=1NuNu (newuidmap eșuează)ParțialDa
Funcționează când unshare(CLONE_NEWUSER) este blocat (EPERM)N/ANuBuildah: Nu / Kaniko: DaDa
Funcționează fără /dev/fuseDa (utilizează overlay2 de nucleu)Doar prin copiere lentă vfsDaDa (overlayfs pe ext4lite)
Suportă docker build (BuildKit)DaDa (dacă userns este permis)Doar construire de imagini (fără runtime)Da
Suportă docker run și docker compose upDaDa (dacă userns este permis)NuDa
Rulează pe Windows și macOS fără o mașină virtuală LinuxNu (necesită WSL2 / VM)Nu (necesită VM Linux)NuDa (whpx, hvf, systrap)

Cerințele exacte ale nucleului gazdă Linux

gvx nu necesită nicio capabilitate ridicată (CapEff: 0000000000000000) și niciun spațiu de nume al utilizatorului. Pe o gazdă Linux, rungv se bazează doar pe trei primitive standard ale nucleului, fără privilegii:

  1. ptrace(PTRACE_ATTACH) asupra propriului fir de execuție copil, o singură dată, în timpul inițializării: systrap pornește un fir de execuție stub local, se atașează la acesta pentru a inițializa contextul de memorie partajată și apoi se detașează. Dacă gazda setează kernel.yama.ptrace_scope = 3 (care dezactivează ptrace la nivelul întregului sistem) sau blochează ptrace printr-un filtru seccomp exterior, inițializarea systrap nu poate fi finalizată. (Cu yama.ptrace_scope = 1, valoarea implicită pe Ubuntu și Debian, gvx funcționează, deoarece trasarea de la părinte la copil este permisă.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Utilizat pentru a intercepta apelurile de sistem ale sistemului invitat din firele de execuție stub ale containerului. Așa cum este descris în Partea 3 a acestei serii, gvx funcționează atât atunci când SECCOMP_FILTER_FLAG_NEW_LISTENER este disponibil, cât și atunci când returnează EBUSY într-un sandbox imbricat.
  3. mmap(..., PROT_EXEC, ...): Utilizat pentru a mapa segmentele ELF ale sistemului invitat și trampolina de memorie partajată a systrap.

Exemplu integral: construirea și testarea unei aplicații multi-container fără root

Următorul script rulează un docker build complet și un test de integrare cu două containere (redis și containerul aplicației) într-o sesiune de utilizator fără privilegii:

 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# Construiți imaginea utilizând overlayfs pe ext4lite
20docker build -t demo-ping:latest .
21
22# Creați o rețea de tip bridge, porniți Redis și verificați conectivitatea dintre containere
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# Rezultat: PONG
27
28# Curățați containerele de lucru și opriți sandbox-ul
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Lecturi suplimentare din această serie

  1. gvx: Rularea containerelor Linux, Docker și Kubernetes într-un singur proces fără privilegii: Prezentarea arhitecturii gvx, a motoarelor de execuție multiplatformă (whpx, hvf, systrap), a VFS2 (erofs, ext4lite, hostfs v2) și a netstack.
  2. Rularea Docker, docker build și docker compose fără root sau spații de nume ale utilizatorilor (această postare)
  3. Rularea Docker-in-Docker în gVisor și în sandbox-uri imbricate cu gvx: Modul în care gvx rezolvă modprobe, iptables-nft, overlay2 pe gofer și SECCOMP_FILTER_FLAG_NEW_LISTENER imbricat (EBUSY) în gVisor (runsc).
  4. Medii Docker izolate și rapide pentru rulările RL ale agenților AI cu gvx: Scalarea mediilor paralele de tip SWE-bench și RL, cu resetarea volumelor .ext4 Copy-on-Write în 100 de milisecunde.