Podman: Linux-Container einfach gemacht, Teil 2

Seite 3: Podman und User Namespaces

Inhaltsverzeichnis

User Namespaces verdienen besondere Erwähnung, da deren Verwendung sowohl der Sicherheit als auch der Portabilität dient. User Namespaces erlauben es, die User ID (UID) und Group ID (GID) innerhalb des Containers neu zuzuordnen. Damit sind die UID und GID innerhalb des Containers eine andere als außerhalb. Wie dient das der Sicherheit?

Um das zu erläutern, erstellt man zunächst eine Datei, die Anwender ausschließlich mit Root-Zugriff lesen und schreiben dürfen.

$ sudo bash -c "echo Heise > /tmp/heise.txt"
$ sudo chmod 600 /tmp/heise.txt
$ sudo ls -l /tmp/heise.txt
-rw-------. 1 root root 6 May 2 13:52 /tmp/heise.txt

Nun mountet man die zuvor erstellte Datei in einen neuen Container und User Namespace.

$ sudo podman run -it -v /tmp/heise.txt:/tmp/heise.txt:Z --uidmap 0:100000:5000 alpine sh
/ # whoami
root
/ # ls -l /tmp/heise.txt
-rw------- 1 nobody nobody 6 May 2 11:52 /tmp/heise.txt
/ # cat /tmp/heise.txt
cat: can't open '/tmp/heise.txt': Permission denied

Hier gibt es ein paar Sachen zu verdauen, angefangen beim Aufruf von Podman. Die Option --uidmap 0:100000:5000 instruiert Podman, einen neuen User Namespace für den Container mit der Zuordnung 0:100000:5000 anzulegen. Die Zuordnung legt fest, dass ab der Host UID 100000 insgesamt 5000 UIDs dem Container zugeordnet werden, beginnend bei UID 0. Damit ist die UID 1 innerhalb des Containers die UID 100001 außerhalb. Wie im whoami-Aufruf zu sehen, hat man trotz Aufenthalts innerhalb des Containers root und dennoch keinen Zugriff auf die /tmp/heise.txt-Datei. Das liegt daran, dass Linux jedes Objekt einer UID die nicht in dem User Namespace zugeordnet ist als UID "nobody" betrachtet. Damit hat root innerhalb des Containers und damit innerhalb des User Namespace keinen Zugriff auf die gemountete Datei.

Am Beispiel lassen sich die zwei Eigenschaften von User Namespaces ableiten. Zum einen gewinnen Nutzer an Sicherheit, da der Root-Container-Prozess auf dem Host keine Root-Rechte innehat und selbst bei Ausbruch aus dem Container nur begrenzt Schaden anrichten kann. Zum anderen gewinnt man an Portabilität, da viele Anwendungen voraussetzen, root ausgeführt zu werden. Das ist damit nach wie vor möglich – nur wesentlich sicherer.

Control Groups oder kurz Cgroups sind ein Mechanismus des Linux-Kerns, um Gruppen von Prozessen hierarchisch zu organisieren und deren Ressourcen zu limitieren und zu überwachen. Cgroups umfasst verschiedene Ressourcen wie den Arbeitsspeicher, CPU-Nutzung, Netzwerk, die Anzahl von Prozessen und vieles andere mehr. Prozesse innerhalb einer Cgroup unterliegen den gleichen Bedingungen, zum Beispiel einer maximalen Arbeitsspeichernutzung von 4 GByte. Die hierarchische Struktur ermöglicht eine feinere Verteilung der Ressourcen, sodass Prozesse in Gruppe A mehr relative CPU-Zeit bekommen als in Gruppe B.

Podman bietet einige Optionen über die Podman-API und über die Kommandozeile an, um mit Cgroups die Ressourcen eines Containers zu kontrollieren: Zum Beispiel --cpu-count=n, um die Anzahl der zur Verfügung stehenden CPUs auf n zu beschränken, oder --memory für den zur Verfügung stehenden Arbeitsspeicher.

Während Namespaces die Ressourcen und deren Sichtbarkeit isolieren, können Anwender mit Cgroups die zur Verfügung stehenden Ressourcen weiter limitieren, kontrollieren und zuweisen. Für die Sicherheit eines Containers ist das Zusammenspiel von Namespaces und Cgroups der springende Punkt. So kann ein kompromittierter Container nur begrenzt Schaden anrichten, da bestimmte Ressourcen gar nicht zur Verfügung stehen (zum Beispiel laufende Prozesse oder Benutzer außerhalb des Containers) und die zur Verfügung stehende Ressourcen begrenzt sind.

Ein berühmtes Angriffsszenario, um einen laufenden Dienst, zum Beispiel einen Webserver, in die Knie zu zwingen, ist die sogenannte Forkbomb. Sobald ein Angreifer die Kontrolle über einen Prozess gewonnen hat, versucht er so viele Kopien des Prozesses wie möglich anzufertigen, um alle Systemressourcen zu verbrauchen und somit das System zu blockieren. Mit Cgroups ist eine Forkbomb nicht möglich, da die maximale Anzahl an Prozessen innerhalb eines Containers stets begrenzt ist.

Capabilities sind ein rechtebasiertes Sicherheitskonzept von Linux. Die Capabilities (Fähigkeiten) eines Prozesses können Leser sich als eine Liste von Befugnissen vorstellen, die bestimmt, ob ein Prozess eine bestimmte Operation ausführen darf oder auf bestimmte Ressourcen zugreifen kann.

Vor Linux 2.2 gab es zwei Klassen von Prozessen: privilegierte Prozesse mit der User ID von 0 und nicht privilegierte Prozesse. Doch diese "Alles oder nichts"-Einteilung schlägt schnell an ihre Grenzen, da ein ordentliches, rechtebasiertes Sicherheitskonzept eine feingranulare Verteilung von Befugnissen bedarf. So soll ein bestimmter Benutzer zum Beispiel auf den Drucker zugreifen dürfen, aber nicht die Konten anderer Benutzer löschen können.

Capabilities spielen für die Sicherheit eines Containers eine entscheidende Rolle, da dadurch die Befugnisse eines Containers start beschränkt werden können, um den potenziellen Schaden eines kompromittierten Containers noch weiter einzudämmen. Standardmäßig setzt Podman nur 14 der knapp 40 Capabilities ein, die man bequem über --cap-add hinzufügen oder --cap-drop entfernen kann. Zum Beispiel ist die SYS_NICE-Capability nicht gesetzt, sodass ein Prozess die Prioritäten nicht erhöhen kann.

$ sudo podman run alpine nice -n -20 echo "Hallo Heise"
nice: setpriority(-20): Permission denied

Mit der --cap-add-Optionen können Entwickler dem Container die Befugnis zuweisen.

$ sudo podman run --cap-add SYS_NICE fedora nice -n -20 echo "Hallo Heise"