GoSuda

Изпълнение на Docker, docker build и docker compose без root права или user namespaces

By Lemon Mint views ...

Бързо решение: Сигурен непривилегирован Docker в три стъпки

Ако работите в заключен Linux контейнер, CI runner, Kubernetes Pod или среда за AI агент, където root, sudo, user namespaces (unshare(CLONE_NEWUSER)), mount(2) и /dev/fuse са блокирани, gvx (rungv) е сигурното решение с дълбока защита (defense-in-depth) за стартиране на пълен docker:dind engine в потребителското пространство.

Защо gvx е безопасен по дизайн (не е заобиколно решение за ескалация на привилегии): За разлика от монтирането на хостовия /var/run/docker.sock или изпълнението на docker run --privileged (които дават контрол, еквивалентен на root, върху ядрото на хоста), gvx не заобикаля и не отслабва никоя политика за сигурност на хоста (PR_SET_NO_NEW_PRIVS, seccomp, AppArmor или ограниченията за non-root UID). Вместо това rungv заключва собствения си хост процес (PR_SET_NO_NEW_PRIVS + строг BPF seccomp allowlist) и стартира dockerd в рамките на безопасното по отношение на паметта gVisor (Sentry) потребителско ядро на Google. Всички възможности на гост UID 0, монтиранията overlayfs (ext4lite) и правилата на iptables остават строго изолирани в рамките на sandbox процеса.

 1# 1. Инсталиране на rungv в потребителското пространство (предварително компилирано SLSA-атестирано издание или go install)
 2# Опция A: Проверка и извличане на предварително компилирания SLSA-атестиран двоичен файл от 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# Опция Б: Или инсталиране директно от изходния код с Go
 9CGO_ENABLED=0 GOBIN=~/.local/bin go install z.gfire.dev/gvx/cmd/rungv@latest
10
11# 2. Стартиране на docker:dind в gvx с единичен ext4lite volume във файл
12#    Препраща ~/.gvx/docker.sock (режим 0600) към порт 2375 на госта
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. Насочване на стандартния Docker CLI и Docker Compose към потребителския socket
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# Изход: overlayfs Alpine Linux ... (containerized)
26
27docker build -t app:test .
28docker run --rm app:test
29docker compose up -d

Защо стандартните rootless инструменти се провалят в ограничени среди

Когато инженер или автономен агент за писане на код има нужда да изпълни docker build, docker run или docker compose в укрепен контейнер или многопотребителски runner, стандартните заобиколни решения се провалят на пет отделни бариери на ниво ядро.

1. Няма сокет на хостовия Docker демон

Изпълнението на docker ps или docker build се проваля незабавно, когато хостовият /var/run/docker.sock не е монтиран в средата:

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

2. PR_SET_NO_NEW_PRIVS блокира sudo и setuid помощните програми

Укрепените контейнери (securityContext.allowPrivilegeEscalation: false в Kubernetes, --security-opt=no-new-privileges в Docker, или exebox / bwrap sandboxes) задават битовете Linux PR_SET_NO_NEW_PRIVS в дървото на процесите. Опитът за ескалация чрез sudo или setuid двоични файлове (newuidmap, newgidmap, fusermount3) се проваля:

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. Политиките за seccomp и sysctl блокират unshare(CLONE_NEWUSER)

Инструментите, рекламирани като "rootless" (Rootless Docker, Podman, Buildah, nerdctl, bubblewrap), все още разчитат на Linux user namespaces (CLONE_NEWUSER). Те се провалят, когато:

  • Профилът по подразбиране на seccomp на външния контейнер блокира unshare(2) или clone(CLONE_NEWUSER).
  • Ubuntu 24.04+ активира kernel.apparmor_restrict_unprivileged_userns = 1.
  • Debian или RHEL възли задават kernel.unprivileged_userns_clone = 0 или user.max_user_namespaces = 0.
  • newuidmap не може да записва в /proc/self/uid_map, защото PR_SET_NO_NEW_PRIVS е активен.

Получените грешки изглеждат така:

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) е блокиран без CAP_SYS_ADMIN на хоста

Дори ако средата позволява CLONE_NEWUSER, монтирането на нов /proc във вложено mount namespace се проваля, ако външният контейнер е маскирал поддървета на /proc (/proc/acpi, /proc/kcore, /proc/keys) или е премахнал mount(2) от seccomp:

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. Липсата на /dev/fuse нарушава fuse-overlayfs

Rootless контейнерните engines, които не могат да монтират ядрен overlayfs, преминават към fuse-overlayfs. В минимални контейнери или Kubernetes pods без проброс на устройството /dev/fuse, fuse-overlayfs се проваля незабавно:

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

Как gvx изпълнява docker:dind изцяло в потребителското пространство

gvx преодолява и петте бариери на ниво ядро, защото не изисква от ядрото на Linux на хоста да създава namespaces, да монтира файлови системи или да конфигурира мрежови интерфейси. Вместо това rungv стартира вътрешно потребителско Linux ядро (gVisor Sentry) и потребителски мрежов стек (netstack) в рамките на един обикновен непривилегирован процес.

Изпълнение на непривилегирован Docker-in-Docker с gvx

1. Namespaces в паметта и таблица на монтиранията (VFS2)

Когато dockerd, containerd и runc се изпълняват в rungv, systrap прихваща техните системни извиквания и ги насочва към вътрешното ядро Sentry, а не към ядрото на хоста:

  • unshare(2), clone(2), pivot_root(2), chroot(2) и mount(2) работят върху йерархията на задачите в паметта на Sentry и върху таблицата на монтиранията VFS2 (procfs, sysfs, tmpfs, devpts, cgroupfs v2), докато хост процесът rungv работи под вашия непривилегирован хост UID.

2. Нативен overlayfs върху ext4lite томове от единичен файл

Когато подадете -v gvx-docker:/var/lib/docker (или --mount type=volume,src=/disk/gvx-docker.ext4,dst=/var/lib/docker), gvx монтира вградената си файлова система ext4lite в /var/lib/docker.

Тъй като ext4lite реализира ext4 файлова система с JBD2 журналиране в рамките на единствен файл на хоста (gvx-docker.ext4), с поддръжка на разширените атрибути trusted.overlay.*, символните whiteout устройства (0:0), d_type и RENAME_EXCHANGE, dockerd използва собствения си драйвер за съхранение overlayfs в госта. Многоетапните операции docker build споделят Copy-on-Write слоеве без /dev/fuse и без експлозията на дисковото пространство, характерна за резервния драйвер vfs на Docker.

3. Потребителски мост docker0 и iptables-legacy

Когато docker:dind стартира в gvx:

  • dockerd на госта създава моста docker0 (172.17.0.1/16), свързва виртуални veth двойки за вложените контейнери и програмира веригите NAT (DOCKER, POSTROUTING) и FILTER с мачове addrtype и conntrack в потребителския netstack на gvx.
  • Подаването на -p unix:~/.gvx/docker.sock:2375 свързва хостов Unix domain socket (режим 0600) и препраща връзките директно към порт 2375 на dockerd в sandbox средата.

Сравнение с съществуващи инструменти за контейнери

Възможност / ОграничениеСтандартен Docker (dockerd)Rootless Docker / PodmanKaniko / Buildah (непривилегирован)gvx (rungv + docker:dind)
Изисква хостов root или sudoДаНеНеНе
Работи с PR_SET_NO_NEW_PRIVS=1НеНе (newuidmap се проваля)ЧастичноДа
Работи, когато unshare(CLONE_NEWUSER) е блокиран (EPERM)Не се прилагаНеBuildah: Не / Kaniko: ДаДа
Работи без /dev/fuseДа (използва ядрен overlay2)Само чрез бавен драйвер vfsДаДа (overlayfs върху ext4lite)
Поддържа docker build (BuildKit)ДаДа (ако userns е разрешен)Само изграждане на образи (без runtime)Да
Поддържа docker run и docker compose upДаДа (ако userns е разрешен)НеДа
Работи на Windows и macOS без Linux VMНе (изисква WSL2 / VM)Не (изисква Linux VM)НеДа (whpx, hvf, systrap)

Точни изисквания към ядрото на Linux хост

gvx не изисква никакви повишени възможности (CapEff: 0000000000000000) и никакви user namespaces. На Linux хост rungv разчита единствено на три стандартни непривилегировани примитива на ядрото:

  1. ptrace(PTRACE_ATTACH) върху собствената му дъщерна нишка еднократно при инициализация: systrap стартира локална помощна нишка-стъб при старт, прикрепя се към нея, за да инициализира споделения контекст на паметта, и се отделя. Ако хостът зададе kernel.yama.ptrace_scope = 3 (което деактивира всички ptrace в системата) или блокира ptrace във външен seccomp филтър, инициализацията на systrap не може да завърши. (При yama.ptrace_scope = 1, стойността по подразбиране на Ubuntu и Debian, gvx работи успешно, тъй като проследяването от родител към дете е разрешено.)
  2. seccomp(SECCOMP_SET_MODE_FILTER): Използва се за прихващане на системни извиквания на госта от нишките-стъбове на контейнерите. Както е описано в Част 3 от тази серия, gvx работи както когато SECCOMP_FILTER_FLAG_NEW_LISTENER е наличен, така и когато връща EBUSY във вложена sandbox среда.
  3. mmap(..., PROT_EXEC, ...): Използва се за картиране на ELF сегментите на госта и на споделения паметов трамплин на systrap.

Пример от край до край: Изграждане и тестване на многоконтейнерно приложение без root

Следният скрипт изпълнява пълен docker build и интеграционен тест с два контейнера (redis + контейнер с приложението) в непривилегирована потребителска сесия:

 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# Изграждане на образа с overlayfs върху ext4lite
20docker build -t demo-ping:latest .
21
22# Създаване на мрежа с мост, стартиране на Redis и проверка на връзката между контейнерите
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# Изход: PONG
27
28# Почистване на работните контейнери и спиране на sandbox средата
29docker rm -f cache
30docker network rm demo-net
31kill -INT $DIND_PID

Допълнително четене в тази серия

  1. gvx: Изпълнение на Linux контейнери, Docker и Kubernetes в един непривилегирован процес: Архитектурен преглед на gvx, неговите междуплатформени изпълнителни engines (whpx, hvf, systrap), VFS2 (erofs, ext4lite, hostfs v2) и netstack.
  2. Изпълнение на Docker, docker build и docker compose без root или User Namespaces (тази публикация)
  3. Изпълнение на Docker-in-Docker в gVisor и вложени sandbox среди с gvx: Как gvx решава modprobe, iptables-nft, overlay2 върху gofer и вложения SECCOMP_FILTER_FLAG_NEW_LISTENER (EBUSY) в gVisor (runsc).
  4. Бързи изолирани Docker среди за RL rollouts на AI агенти с gvx: Мащабиране на паралелни SWE-bench и RL rollout среди с 100-милисекундни копирай-при-запис (copy-on-write) .ext4 томове с нулиране.