Führen Sie Ihre Container als root oder mit einer normalen Benutzerrolle aus? Hierbei scheint es sich um eine ganz einfache Frage zu handeln. Aber geben Sie keine voreilige Antwort. Haben Sie das Bedrohungsmodell wirklich klar vor Augen? Vermutlich nicht. Dieser Beitrag soll zur Verdeutlichung beitragen.

Bevor Sie die oben gestellte Frage beantworten können, müssen Sie herausfinden, ob wir über die Container Engine (Podman, Docker, CRI-O, containerd usw.), den Prozess innerhalb des Containers (apache, postgresql, mysql usw.) oder die Prozess-ID sprechen, der der Container zugeordnet ist (die 3 können unterschiedlich sein). Auf den ersten Blick ist dies möglicherweise nicht offensichtlich. Die Container Engine oder der zugehörige Teilprozess in Containern kann von praktisch allen Benutzerrollen ausgeführt werden.

Containers illustrationErreichen Sie mehr mit dem Universal Base Image (UBI) von Red Hat

Root

Mit der Einführung von Podman wurden rootless-Container zur Realität. Da Podman Container als direkte Teilprozesse von sich selbst erstellt, lassen sich die 4 möglichen Optionen leicht nachvollziehen. Aber was ist rootless? Um rootless verstehen zu können, müssen Sie root innerhalb eines Containers verstehen. Um root innerhalb eines Containers zu verstehen, müssen Sie root außerhalb eines Containers verstehen. Diese Erklärung ist nicht sehr verständlich.

Die folgende Tabelle zeigt root innerhalb und außerhalb des Containers (Danke an Vincent Batts, der mir diese Konzepte auf der DevConf.US 2019 vorgestellt hat). Mit diesem Framework sollten Sie ein Verständnis für rootless entwickeln:

 

Table 1: Root inside and outside the container showing how users appear to the system

Im oben aufgeführten Beispiel sind einige interessante Punkte zu berücksichtigen.Erstens werden die Befehlszeilenoptionen -i (interactive) -t (terminal) und -u (user) verwendet. In Kombination stellen diese Optionen ein interaktives Terminal innerhalb des Containers bereit und geben an, dass der containerisierte Prozess als Benutzer „sync“ ausgeführt werden soll. Weitere Informationen hierzu erhalten Sie, indem Sie den Befehl „man podman-run“ eingeben. 

Zweitens bedeutet die Verwendung von $, dass die Shell mit normalen Benutzerberechtigungen ausgeführt wird, während # darauf hinweist, dass die Shell als root ausgeführt wird. Hierbei handelt es sich um Unix-Traditionen, mit denen sich root innerhalb und außerhalb des Containers einfacher erklären lassen. 

Drittens befindet sich im Beispiel oben Podman per Definition außerhalb des Containers und wird als root oder mit normalen Benutzerberechtigungen (fatherlinux) ausgeführt, während innerhalb des Containers die Bash als root oder mit normalen Benutzerberechtigungen (sync) ausgeführt wird. Die Nutzer in der Datei /etc/passwd auf dem Container-Host werden für Podman verwendet, während die Nutzer in der Datei /etc/passwd im Container Image im ausgeführten Container verwendet werden. Mit anderen Worten implizieren diese beiden passwd-Dateien, dass wir 2 Benutzergruppen haben können: eine Gruppe außerhalb des Containers und eine Gruppe innerhalb des Containers. Genau das ermöglichen User Namespaces.

User Namespaces

Wenn der Kernel einen laufenden Prozess innerhalb eines Containers erstellt, ordnet der User Namespace die Benutzer-ID der containerisierten Prozesse einer anderen Benutzer-ID außerhalb des Containers zu. Hier ein Beispiel dazu:

 

Table 2: Showing user IDs and username inside and outside container

Die Befehlszeilenoptionen lauten -i (interactive) -d (detach) und -u (user). In Kombination führen diese Optionen den Container im Hintergrund aus und geben an, dass der containerisierte Prozess als Benutzer „sync“ ausgeführt werden soll. Auch hierzu erhalten Sie weitere Informationen, indem Sie den Befehl "man podman-run" eingeben. 

Podman gibt uns auch einen wirklich coolen Unterbefehl namens top, mit dem wir den Benutzer auf dem Container-Host dem Benutzer im ausgeführten Container zuordnen können. Das Beispiel oben zeigt, dass wir beim Ausführen eines Containers als root den Benutzer „sync“ (uid 5) im Container dem Benutzer „sync“ (uid 5) im zugrunde liegenden Container-Host zuordnen. Dies heißt, wenn ein Breakout für einen Prozess in diesem Container auftritt, kann er mit den Berechtigungen des echten sync-Benutzers ausgeführt werden. 

Wenn wir hingegen genau denselben Container wie ein regulärer Benutzer (fatherlinux) ausführen, ordnet er den Benutzer sync (uid 5) im laufenden Container der uid 100004 auf dem Host des zugrunde liegenden Containers zu. Warten Sie! Warum wurde der Benutzer sync (uid 5) nicht Fatherlinux (uid 1000) zugeordnet?

Nun, die kurze Antwort lautet, weil bei neueren Kerneln und neueren shadow-utils-Paketen (useradd, passwd, etc.) jedem neuen Benutzer ein Bereich von Benutzer-IDs zur Verfügung gestellt wird. Traditionell hatte auf einem Unix-System jeder Benutzer nur eine ID, aber jetzt ist es möglich, dass jedem Benutzer Tausende von UIDs zur Verwendung in Containern zur Verfügung stehen. 

Dies ist nützlich, wenn ein Container mehrere Benutzer verwendet. Beispiele hierfür sind die gemeinsame Ausführung von Apache und MySQL in einem einzelnen Container oder Pod oder die Ausführung eines Sidecar-Containers mit einem Agenten, der unter einem anderen Benutzer ausgeführt wird. Aber woher kommt diese Zuordnung? Aus 2 Dateien: /etc/subuid und /etc/subgid. Einträge werden in diesen Dateien erstellt, wenn Benutzer hinzugefügt werden, über den Befehl usermod oder manuell von einem Systemadministrator.

Optionaler tiefer Einblick in Benutzer-IDs

Hier ist ein Beispiel für Einträge in meinem System. Mit den folgenden Einträgen kann der Benutzer vaterlinux bis zu 65.535 Benutzer-IDs in Containern realen Benutzer-IDs auf dem System ab 100.000 zuordnen. Standardmäßig ist shadow-utils (useradd, passwd usw.) dieser Bereich von Benutzer-IDs nur für einen Benutzer reserviert. Der Befehl useradd reserviert den nächsten Bereich für den nächsten Benutzer. In diesem Beispiel ist das der Benutzer fred, beginnend mit der Benutzer-ID 165536:

cat /etc/subuid Fatherlinux:100000:65536 fred:165536:65536 cat /etc/subgid Fatherlinux:100000:65536 fred:165536:65536

Sie können diese Karte auch innerhalb eines Containers anzeigen:

 

Table 3: More on users inside and outside the container

Beachten Sie, dass der vollständige Benutzer-ID-Bereich im Container verfügbar ist (4294967295 == 32 Bit), wenn Podman als root ausgeführt wird. Wenn Podman jedoch als Fatherlinux ausgeführt wird, ordnet es root im Container dem Benutzer vaterlinux (1000) und dem Benutzer sync (uid 5) einer UID im Bereich von 100.000 und 165.535 zu. 

Dies ist eine großartige Sicherheitsfunktion, da die Container-Engine und der containerisierte Prozess im ausgeführten Container jetzt als unterschiedliche, nicht privilegierte Benutzer ausgeführt werden. Die Benutzer-IDs von 100.000 bis 165.535 haben keine besonderen Berechtigungen auf dem System, nicht einmal als Benutzervaterlinux (1000). Dies bedeutet, dass ein Prozess im Container, der ausbricht, auf dem Container-Host stark eingeschränkt wird. 

Eine weitere Frage, die sich stellt, lautet: Können dem System die UIDs ausgehen, wenn Sie eine Reihe von Benutzern hinzufügen? Die kurze Antwort lautet: Ja. Dies ist jedoch unwahrscheinlich, da UIDs durch eine 32-Zahl mit 4 Milliarden UIDs dargestellt werden. Das bedeutet, dass Sie einem System bis zu 65.535 Benutzer hinzufügen können (4294967295 dividiert durch 65535). Dies sollte für die meisten Anwendungsfälle ausreichen.

Sehen wir uns eine letzte Nuance von rootless-Containern an. Die Datei /etc/subuid wird verwendet, um den Benutzer innerhalb des Containers einem Benutzer außerhalb des Containers zuzuordnen. Der Benutzer (fatherlinux im folgenden Beispiel) muss jedoch im Container-Image definiert sein, da Podman ansonsten den Container nicht starten kann: 

podman run --user Fatherlinux -it ubi8 bash

Ausgabe:

Benutzervaterlinux kann nicht gefunden werden: Keine übereinstimmenden Einträge in der Datei passwd

Sie müssen eine Benutzer-ID im Container angeben, der in der Datei /etc/passwd im Container-Image vorhanden ist. Dies ist ein weiteres Beispiel dafür, wie Container untrennbar mit dem Betriebssystem im Container verknüpft sind und die Trennung vom Betriebssystem des Container-Hosts aufrechterhalten wird. Containers sind Linux.

Containerverteidigung im Detail

Dieses Konzept ist mit dem Daemon docker aufgrund des Client-Server-Modells nicht leicht zu verstehen. Mit dem Client-Server-Modell docker können wir einen Container als root ausführen, auch wenn wir den Befehl als regulärer Benutzer ausführen. Das liegt daran, dass der docker -Daemon als root ausgeführt wird und daher über alle Berechtigungen von root verfügt. Dies sollte jetzt viel klarer sein. Führen Sie zur Demonstration die folgenden Befehle aus:

 

Table 4: Commands to check user IDs

Um sicherzustellen, dass ein Benutzer, der einen Container ausführt, keinen root-Zugriff auf Ihren Host erhält, müssen Sie die Container-Engine und den containerisierten Prozess als Nicht-root-Benutzer ausführen. Dadurch werden mehrere Sicherheitsebenen zwischen dem Service (httpd, MySQL, usw.) und den privilegierten Ressourcen im Betriebssystem bereitgestellt. Das Ausführen der Container-Engine als Nicht-root-Benutzer ist eine Verteidigungsebene, während die Ausführung des Prozesses im Container als ein anderer Nicht-root-Benutzer eine weitere Verteidigungsebene bietet. 

Dan Walsh untersucht dies in diesem Artikel ausführlicher: Running rootless Podman as a non-root User. Auf einer übergeordneten Ebene können Sie eine rootless-Container-Engine wie Podman als Ihr Benutzerkonto ausführen. Anschließend können Sie innerhalb des Containers eine virtuelle Gruppe von Benutzern verwenden, die einer Gruppe von Benutzer-IDs zugeordnet sind, die nur von Ihrem Konto für die containerisierten Prozesse gesteuert werden. 

Jetzt sollten Sie die Befugnisse von root innerhalb und außerhalb des Containers besser verstehen.


About the author

At Red Hat, Scott McCarty is Senior Principal Product Manager for RHEL Server, arguably the largest open source software business in the world. Focus areas include cloud, containers, workload expansion, and automation. Working closely with customers, partners, engineering teams, sales, marketing, other product teams, and even in the community, he combines personal experience with customer and partner feedback to enhance and tailor strategic capabilities in Red Hat Enterprise Linux.

McCarty is a social media start-up veteran, an e-commerce old timer, and a weathered government research technologist, with experience across a variety of companies and organizations, from seven person startups to 20,000 employee technology companies. This has culminated in a unique perspective on open source software development, delivery, and maintenance.

Read full bio