Unprivilegierte Container in einer Multiuser Umgebung

In der Standardkonfiguration ist es einem Docker-User ein leichtes, sich über einen Container root-Rechte auf dem Hostsystem zu verschaffen.

Um das zu vermeiden gibt es zwei Möglichkeiten, rootless mode und userns-remap. Die erste Variante benötigt nicht einmal root-Rechte um Docker zu installieren, es wird alles im Homeverzeichnis des Users installiert. Das macht es zwar relativ einfach, aber leider ungeeignet für größere Umgebungen wo eine beliebige Anzahl von Usern, die idealerweise noch aus einem Verzeichnisdienst kommen, den Docker-Dienst nutzen können soll.

Deutlich interessant ist hier userns-remap. Dabei läuft der Docker-Daemon weiterhin als root, die Container werden jedoch im Namespace des startenden Users angelegt. Das hat natürlich ein paar Einschränkungen, die ich hier nicht nochmal aufzählen möchte, die Dokumentation tut das schon ganz gut.

Um userns-remap einzurichten wird lediglich zusätzlich das Paket uidmap benötigt.

sudo apt-get install uidmap

Dann muss der Docker-Daemon konfiguriert werden.

sudo vi /etc/docker/daemon.json
{
"userns-remap": "default"
}

Einen Haken hat die Sache noch: Ein User kann immer noch einen Container im Host-Namespace erstellen indem er an sein docker run-Kommando den Parameter –userns=host anhängt. Den kann man jedoch recht einfach unterbinden, es hat schon jemand ein auth-Plugin für genau diesen Zweck geschrieben.

Leider gibt es das Plugin nicht als Binary oder Paket, man muss es sich selbst kompilieren. Das ist aber in einem Container kein Problem:

docker pull golang
git clone https://github.com/dandare100/docker-denyusernshost
cd docker-denyusernshost/
docker run --rm -it -v "$PWD":/usr/src/myapp -w /usr/src/myapp golang bash
go mod init
go mod vendor
go build -o denyusernshost

Danach hat man im Quellcodeverzeichnis das kompilierte binary denyusernshost liegen. Die Datei kopiert man an eine geeignete Stelle, zum Beispiel /usr/local/sbin/. Dann noch ein systemd unit-File dafür angelegt:

sudo vi /etc/systemd/system/denyusernshost.service
[Unit]
Description=Docker denyusernshost
Documentation=https://github.com/dandare100/docker-denyusernshost
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/sbin/denyusernshost
Restart=on-failure
[Install]
WantedBy=multi-user.target

Systemd neu laden und den Dienst starten

sudo systemctl daemon-reload
sudo systemctl enable denyusernshost
sudo systemctl start denyusernshost

Jetzt noch das Plugin beim Docker-Daemon eintragen:

sudo vi /etc/docker/daemon.json
{
"userns-remap": "default",
"authorization-plugins": ["denyusernshost"]
}

Zu guter Letzt Docker neu starten damit die Konfiguration aktiv wird

sudo systemctl restart docker

Und schon können nur noch unprivilegierte Container ohne root-Rechte gestartet werden. Versucht ein User das zu umgehen bekommt er eine entsprechende Fehlermeldung, und der Versuch wird protokolliert.

ini-sc@slurmtest01:~$ docker run --userns=host --rm -it ubuntu bash
docker: Error response from daemon: authorization denied by plugin denyusernshost: userns=host is not allowed.

Um nicht jeden User der docker-Gruppe hinzufügen zu müssen (was gar nicht praktikabel ist, wenn die User aus dem Verzeichnisdienst kommen) kann man den Zugriff über ACL auf dem Docker-Socket regeln.

setfacl -m g:'domain users':rw /var/run/docker.sock

Die Einstellung ist allerdings nach dem nächsten Neustart des Hosts weg, da /var/run ein dynamisches Dateisystem ist und der Socket beim Start des Daemons erzeugt wird. Das können wir aber ebenfalls mit einer systemd-Unit machen.

sudo vi /etc/systemd/system/docker-setfacl.service
[Unit]
Description=Set ACL for docker access
After=docker.service
[Service]
Type=oneshot
ExecStart=/usr/bin/setfacl -m g:'domain users':rw /var/run/docker.sock
RemainAfterExit=true
StandardOutput=journal
[Install]
WantedBy=multi-user.target

Auch hier wieder systemd neu laden und starten

sudo systemctl daemon-reload
sudo systemctl enable docker-setfacl
sudo systemctl start docker-setfacl

Damit wird die ACL nach dem Neustart neu gesetzt nachdem der Docker-Daemon gestartet wurde.

Veröffentlicht von

Gerald Schneider

Diplom-Informatiker (DH) in Rostock. Ich blogge über Entwicklung, Internet, mobile Geräte und Virtualisierung.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert