12k
All articles

Docker auf einem Linux-System absichern

Mitgliedschaft in der Docker-Gruppe gewährt Root-Rechte. Sichern Sie Docker unter Linux mit Rootless-Modus, sudo-Regeln, Podman, Container-Härtung und Audit-Checkliste.

OpenReplay Team
OpenReplay Team
Docker auf einem Linux-System absichern

Wer einen Benutzer zur Gruppe docker hinzufügt, verschafft ihm Root-Rechte auf dem System. Das sind nicht „fast Root-Rechte“, sondern vollständige, denn jeder, der mit dem Docker-Socket kommunizieren kann, kann das Dateisystem des Hosts in einen Container einhängen und dort bearbeiten.

Sie haben usermod -aG docker $USER ausgeführt, um nicht mehr ständig sudo eintippen zu müssen, und Dockers eigene Post-Installationsanleitung hat genau das empfohlen. Dieselbe Linux-Post-Installationsseite für Docker Engine warnt allerdings auch in einem Hinweiskasten, den die meisten nur überfliegen, dass die Mitgliedschaft in dieser Gruppe Root-Rechten gleichkommt. Falls Sie mit den Grundlagen noch nicht ganz vertraut sind, finden Sie im Einsteigerleitfaden zu Docker-Images und -Containern einen Überblick. Dieser Artikel belegt das Risiko mit einem einzigen Befehl und stellt anschließend die Gegenmaßnahmen nach ihrer Wirksamkeit geordnet vor. Zum Schluss folgt eine Checkliste für die Prüfung eines übernommenen Systems.

Das Wichtigste in Kürze

  • Jedes Mitglied der Gruppe docker und jeder Prozess, der unter diesem Benutzer läuft, kann sich mit docker run --rm -it -v /:/host alpine chroot /host sh eine Root-Shell auf dem Host verschaffen.
  • Rootless Docker führt sowohl dockerd als auch Ihre Container innerhalb eines User Namespace aus. Eine Kompromittierung des Daemons oder ein Container-Ausbruch endet dadurch bei einer unprivilegierten Host-UID statt bei Root.
  • Eine eingeschränkte sudoers-Regel für /usr/bin/docker ist zwar weiterhin root-äquivalent, stellt Docker aber eine Passwortabfrage voran und protokolliert jede Verwendung.
  • Podman kommt ohne Daemon aus und läuft standardmäßig rootless, wenn Sie es als normaler Benutzer verwenden. Da seine CLI weitgehend mit der von Docker kompatibel ist, deckt alias docker=podman die meisten alltäglichen Workflows ab.
  • Prüfen Sie auf einem übernommenen Host, wer Mitglied der Gruppe docker ist, unter welchem Benutzer dockerd läuft, welche Berechtigungen der Socket hat und ob der Daemon auf TCP-Port 2375 lauscht.

Warum verleiht die Docker-Gruppe Root-Rechte?

Die Gruppe docker ist root-äquivalent, weil der Docker-Daemon als Root läuft und die Gruppe den Schreibzugriff auf seinen Unix-Socket steuert. Wer auf diesen Socket schreiben kann, kann einem Root-Prozess beliebige Anweisungen erteilen. Wenn Sie die zugrunde liegenden Mechanismen genauer verstehen möchten, erklärt der Leitfaden zu Linux-Dateiberechtigungen, wie die Gruppenberechtigungen einer Datei bestimmen, wer sie öffnen darf. Für Docker ist nur ein Detail entscheidend: Der Socket ist für die Gruppe docker beschreibbar.

Hier der Beweis:

docker run --rm -it -v /:/host alpine chroot /host sh

Dieser Befehl bindet das Wurzelverzeichnis / des Hosts per Bind-Mount in den Container ein und wechselt anschließend per chroot hinein. Sie verfügen nun über eine Root-Shell auf dem echten Dateisystem des Hosts. Von dort aus können Sie /etc/shadow oder /etc/sudoers bearbeiten oder SSH-Schlüssel unter /root hinterlegen. Dafür benötigen Sie weder ein Passwort noch einen Exploit. Jedes Skript, jede Abhängigkeit und jede kompromittierte Editor-Erweiterung, die unter Ihrem Benutzer läuft, kann dasselbe tun. Die Docker-Dokumentation behandelt dieses Thema im Abschnitt Docker daemon attack surface.

Rootless Docker einrichten

Im Rootless-Modus laufen dockerd und sämtliche Container innerhalb eines User Namespace, der Ihrem Benutzerkonto gehört. Damit ist er die direkte Lösung für das Problem mit der Gruppe docker. Der Socket liegt unter $XDG_RUNTIME_DIR/docker.sock statt unter /var/run/docker.sock. RootlessKit erzeugt den Namespace. Die setuid-Hilfsprogramme newuidmap und newgidmap bilden UID 0 im Namespace auf Ihre eigene UID ab und ordnen den übrigen Container-UIDs einen Bereich aus /etc/subuid und /etc/subgid zu. Die Netzwerkkommunikation läuft über einen User-Mode-Netzwerkstack wie slirp4netns, gvisor-tap-vsock oder pasta.

Installieren Sie zunächst die Voraussetzungen und prüfen Sie dann Ihre Bereiche untergeordneter IDs (Subordinate IDs). Laut der Dokumentation zum Rootless-Modus muss Ihrem Benutzer sowohl in /etc/subuid als auch in /etc/subgid ein Bereich von mindestens 65.536 untergeordneten IDs zugewiesen sein.

sudo apt-get install -y uidmap docker-ce-rootless-extras
grep ^$(whoami): /etc/subuid /etc/subgid

Deaktivieren Sie den Root-Daemon und führen Sie anschließend das Setup-Tool als normaler Benutzer aus:

sudo systemctl disable --now docker.service docker.socket
dockerd-rootless-setuptool.sh install
systemctl --user enable --now docker
sudo loginctl enable-linger $USER
export DOCKER_HOST=unix://$XDG_RUNTIME_DIR/docker.sock

Die Zeile mit enable-linger sorgt dafür, dass Ihr Daemon auch nach dem Abmelden weiterläuft. Sobald Rootless Docker funktioniert, entfernen Sie sich mit sudo gpasswd -d $USER docker aus der alten Gruppe. Die offizielle Dokumentation zum Rootless-Modus beschreibt auch das Vorgehen für andere Distributionen. Falls der Client den Daemon danach nicht erreicht, führt Sie der Artikel zur Fehlerbehebung bei „Docker daemon not running“ durch die Prüfung von Socket und systemd.

Der Rootless-Modus setzt unprivilegierte User Namespaces voraus. Ab Ubuntu 24.04 werden diese standardmäßig über AppArmor eingeschränkt. Das apparmor-Paket unter Ubuntu enthält jedoch bereits das Profil, das rootlesskit benötigt. Eine Installation von docker-ce-rootless-extras über apt funktioniert daher ohne zusätzliche Schritte. Installieren Sie dagegen über das Skript get.docker.com/rootless, müssen Sie dieses Profil selbst hinzufügen. Deaktivieren Sie die Einschränkung nicht systemweit mit kernel.apparmor_restrict_unprivileged_userns=0. Weitere Fehlerfälle behandelt die Troubleshooting-Seite zum Rootless-Modus.

Was kostet der Rootless-Modus?

Im Vergleich zu einem Rootful-Daemon ergeben sich vor allem folgende Kompromisse:

EinschränkungWorkaround
Ports unter 1024 können standardmäßig nicht gebunden werdensysctl net.ipv4.ip_unprivileged_port_start=80 oder ein Reverse Proxy
--privileged gewährt keine echten Host-PrivilegienEs gelten nur Capabilities innerhalb des User Namespace
User-Mode-Networking ist langsamer als der Bridge-TreiberDer experimentelle Treiber lxc-user-nic oder Benchmarks vor dem Produktiveinsatz
Kernel vor Version 5.11 benötigen fuse-overlayfsAb Kernel 5.11 wird overlay2 nativ unterstützt

Die Troubleshooting-Seite nennt einige weitere Einschränkungen, etwa fehlende Unterstützung für AppArmor, Checkpoints und Overlay-Netzwerke.

sudo mit einer eingeschränkten Regel verwenden

Wenn der Rootless-Modus Ihren Workload beeinträchtigt, ist sudo für Docker anstelle einer Mitgliedschaft in der Gruppe docker ein Mittelweg. Auch diese Lösung ist root-äquivalent, da sudo docker denselben chroot-Befehl ausführen kann. Sie gewinnen jedoch eine Passwortabfrage und einen Audit-Log-Eintrag für jeden Aufruf. Code, der unbemerkt unter Ihrem Benutzer läuft, hat keinen Zugriff mehr auf den Socket.

sudo gpasswd -d alice docker
sudo visudo -f /etc/sudoers.d/docker
alice ALL=(root) /usr/bin/docker

Diese Regel erlaubt ausschließlich die Docker-Binärdatei und keine beliebigen Befehle. Die Nutzung können Sie mit journalctl _COMM=sudo überprüfen. Die Post-Installationsdokumentation beschreibt außerdem eine newgrp-Methode, bei der Docker ein Gruppenpasswort vorgeschaltet wird, während die CLI weiterhin unter Ihrem eigenen Benutzer läuft.

Auf Podman umsteigen

Podman kommt ohne Daemon aus und läuft standardmäßig rootless, wenn Sie es als normaler Benutzer ausführen. Container laufen als Kindprozesse Ihres Benutzers innerhalb eines User Namespace und nutzen dieselben Zuordnungen aus /etc/subuid. Die CLI orientiert sich so eng an Docker, dass die meisten Befehle unverändert funktionieren.

sudo apt-get install -y podman
alias docker=podman
podman run --rm -p 8080:80 docker.io/library/nginx

Unterschiede zeigen sich eher in Randbereichen: bei vollqualifizierten Image-Namen, bei der Compose-Unterstützung und bei Tools, die einen Docker-Socket erwarten. Testen Sie Ihre Skripte, bevor Sie ein ganzes Team umstellen.

Den Container selbst härten

Unabhängig davon, welchen Daemon Sie einsetzen, sollten Sie die Möglichkeiten des Prozesses im Container begrenzen. Vier Einstellungen decken den Großteil des Risikos ab:

  • ein Nicht-Root-USER,
  • keine Capabilities über die von der Anwendung benötigten hinaus,
  • ein schreibgeschütztes Root-Dateisystem,
  • keine Rechteausweitung über setuid-Binärdateien.
FROM node:22-alpine
WORKDIR /app
COPY --chown=node:node . .
USER node
CMD ["node", "server.js"]
docker run -d \
  --cap-drop=ALL \
  --read-only --tmpfs /tmp \
  --security-opt no-new-privileges \
  -p 8080:8080 myapp

Die Dockerfile-Anweisung USER legt die UID zur Laufzeit fest. --cap-drop=ALL entfernt sämtliche Capabilities. Benötigt eine Anwendung tatsächlich eine davon, fügen Sie sie mit --cap-add wieder hinzu. Diese Anwendung lauscht auf Port 8080 und benötigt daher keine. Durch die Kombination von --read-only und --tmpfs ist nur /tmp beschreibbar. no-new-privileges verhindert, dass setuid-Binärdateien Privilegien erhöhen. Alle diese Optionen sind in der docker run-Referenz dokumentiert.

Ein übernommenes System prüfen

Beantworten Sie auf einem VPS, den jemand anderes eingerichtet hat, vier Fragen:

  1. Wer ist Mitglied der Gruppe docker?
  2. Unter welchem Benutzer läuft dockerd?
  3. Wer kann auf den Socket schreiben?
  4. Lauscht der Daemon im Netzwerk?
getent group docker
ps -eo user,cmd | grep [d]ockerd
ls -l /var/run/docker.sock
sudo ss -ltnp | grep -E ':2375|:2376'
systemctl cat docker | grep -- '-H'
grep -s hosts /etc/docker/daemon.json

Jeder Name, den getent ausgibt, hat Root-Zugriff. Zeigt ps den Benutzer root an, läuft der Daemon im Rootful-Modus. Der Socket sollte die Berechtigungen srw-rw---- root docker aufweisen. Ist er für alle beschreibbar, müssen Sie das sofort beheben.

Ein Listener auf Port 2375 bedeutet, dass die Docker-API ohne TLS offensteht. Aktuelle Docker-Engine-Versionen verweigern mit dieser Konfiguration den Start, auf einem übernommenen Host kann eine ältere Engine sie aber durchaus noch verwenden. Wer diesen Port erreicht, hat Root-Rechte. Ein TLS-Listener auf Port 2376 ist sicherer, doch wer die Client-Schlüssel besitzt, hat ebenfalls Root-Rechte. Folgen Sie der Anleitung zum Schutz des Daemon-Sockets, um auf SSH oder Mutual TLS umzustellen.

Prüfen Sie außerdem, ob Container den Socket einbinden, denn jeder dieser Container ist ein Root-Zugang:

docker ps -q | xargs -r docker inspect \
  --format '{{.Name}} {{range .Mounts}}{{.Source}} {{end}}' | grep docker.sock

Zugriff auf den Docker-Socket ist Root-Zugriff, ohne Wenn und Aber. Führen Sie zunächst auf jedem Rechner, den Sie nutzen, getent group docker aus. Migrieren Sie die betroffenen Benutzer anschließend entweder auf Rootless Docker oder Podman, oder schützen Sie den Socket mit einer protokollierten sudo-Regel.

FAQs

Worin unterscheiden sich Rootless Docker und userns-remap?

Bei userns-remap läuft dockerd weiterhin als Root, und nur die Container laufen unter umgemappten untergeordneten UIDs, etwa unter dem Benutzer dockremap, der angelegt wird, wenn Sie userns-remap in /etc/docker/daemon.json auf 'default' setzen. Die Sicherheitslücke der Gruppe docker wird dadurch nicht geschlossen: Ein Gruppenmitglied kann mit --userns=host das Remapping für einen einzelnen Container deaktivieren und den chroot-Ausbruch ausführen. Im Rootless-Modus läuft dagegen der Daemon selbst unter Ihrem Benutzer.

Können Rootless und Rootful Docker auf demselben Rechner laufen?

Ja. Die beiden Daemons verwenden getrennte Sockets: /var/run/docker.sock für den Rootful-Modus und $XDG_RUNTIME_DIR/docker.sock für den Rootless-Modus. Der Befehl dockerd-rootless-setuptool.sh install legt einen CLI-Kontext namens 'rootless' an und wechselt zu diesem. Mit docker context use default sprechen Sie den Root-Daemon an, mit docker context use rootless wechseln Sie zurück. Solange der Rootful-Daemon weiterläuft, hat jeder, der auf dessen Socket schreiben kann, nach wie vor Root-Rechte.

Werden meine bestehenden Images und Container in Rootless Docker übernommen?

Nein. Der Rootless-Daemon verwendet ein eigenes Datenverzeichnis, standardmäßig ~/.local/share/docker, getrennt vom Verzeichnis /var/lib/docker des Root-Daemons. Images, Container und Volumes, die unter Rootful Docker erstellt wurden, sind für ihn nicht sichtbar. Laden Sie die Images erneut herunter oder exportieren Sie sie mit docker save aus dem Rootful-Daemon und importieren Sie sie mit docker load in den Rootless-Daemon. Passen Sie außerdem alle Backup-Jobs an, die nur /var/lib/docker abdecken.

Warum zeigen Dateien, die von Rootless-Containern geschrieben wurden, auf dem Host ungewöhnliche Besitzer?

Rootless Docker bildet Container-UIDs auf Host-UIDs ab. Root innerhalb des Containers entspricht Ihrer eigenen UID, sodass Dateien, die er in einen Bind-Mount schreibt, Ihnen gehören. Alle anderen Container-UIDs werden in Ihren Bereich aus /etc/subuid abgebildet. Beginnt dieser Bereich bei 100000, wird aus der Container-UID 1000 die Host-UID 100999, deren Dateien Ihr Benutzer nicht direkt bearbeiten kann. Wenn Sie innerhalb eines Containers chown auf 0:0 ausführen, gehören die Dateien wieder Ihrem Benutzer.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.