Einen Container mit weniger Rechten ausführen

DockerBeginner
Jetzt üben

Einführung

Container verpacken Anwendungen und führen sie aus. Das Verpacken allein wählt jedoch keine sichere Prozessidentität. Dieser vorbereitete DeskLite-Worker erzeugt einen kleinen Ticketbericht und läuft derzeit als Root.

Du lernst den grundlegenden Docker-Ablauf kennen und baust und startest den Worker anschließend mit einer Identität ohne Root-Rechte und nur dem für seine Aufgabe benötigten Dateizugriff neu. Docker und ein festes Basisimage sind vorbereitet; weder ein Registry-Konto noch Docker-Vorkenntnisse sind erforderlich.

Das vorbereitete Containerprojekt kennenlernen

In diesem Schritt ermittelst du die Dateien, aus denen die Berichtsaufgabe besteht.

Ein Image ist ein verpacktes Dateisystem mit Startkonfiguration. Ein Container ist eine laufende Instanz eines Images mit eigenen Prozessen und einer beschreibbaren Dateisystemschicht. Ein Dockerfile beschreibt, wie ein Image gebaut wird. Ein Tag wie ticket-report:practice ist ein praktischer Name für ein Image; nach einem erneuten Build kann er einem neuen Image zugeordnet werden.

cd /home/labex/project/ticket-worker
ls

Erwartet werden Dockerfile, worker.sh, input und output. Der vorbereitete Worker liest CSV-Zeilen, schreibt ihre Anzahl in einen Bericht und läuft dann weiter, damit wir ihn untersuchen können.

cat input/tickets.csv

Nach der Kopfzeile gibt es drei Ticketzeilen. Der erwartete Bericht lautet tickets=3.

cat Dockerfile

FROM wählt das zwischengespeicherte Basisimage aus, WORKDIR das Arbeitsverzeichnis des Images, und COPY fügt den Worker hinzu. USER root wählt die Prozessidentität. CMD ist der als Liste dargestellte Standardstartbefehl. Du änderst nur USER.

Der Basistag desklite-base:1.37.0 ist bereits aus einem festen BusyBox-Image zwischengespeichert. Alle folgenden Builds und Starts verwenden lokale Dateien. Docker-Befehle verwenden sudo, um auf den Daemon dieses Labs zuzugreifen. Dieser Zugriff auf dem Host ist weitreichend; er ist von der Anwendungsidentität getrennt, deren Rechte wir innerhalb des Containers reduzieren.

Den vorhandenen Worker bauen und starten

In diesem Schritt stellst du den funktionierenden, aber mit zu vielen Rechten ausgestatteten Ausgangszustand her.

docker build liest das Dockerfile und einen Build-Kontext, also das Verzeichnis mit den für COPY verfügbaren Dateien. Hier bedeutet . das aktuelle Verzeichnis. -t weist den lokalen Image-Tag zu:

sudo docker build -t ticket-report:practice .

Erwartet wird ein erfolgreicher Build. Das Image existiert, aber noch läuft kein Worker.

docker run erstellt einen Container. -d führt ihn im Hintergrund aus, und --name gibt ihm einen wiederverwendbaren Namen. --network none deaktiviert das Netzwerk, da dieser Bericht nur Dateien benötigt.

Ein Bind-Mount macht einen Hostpfad unter einem Containerpfad zugänglich. Jede Angabe --mount beschreibt Typ, Quelle (src) und Ziel (dst). Dieser Ausgangszustand macht beide Verzeichnisse beschreibbar zugänglich, da keine Option readonly vorhanden ist. Später schränken wir die Eingabe ein.

sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice

Docker gibt eine Container-ID aus. Sie ist nicht die Benutzer-ID des Workers. Der ursprüngliche Worker sollte seinen Bericht erzeugen, den wir als Nächstes untersuchen.

Die tatsächliche Laufzeitidentität untersuchen

In diesem Schritt unterscheidest du einen erfolgreichen Bericht von einer Laufzeit mit minimalen Rechten.

docker ps listet laufende Container auf:

sudo docker ps

Suche nach ticket-worker. docker logs liest die Standardausgabe der Anwendung:

sudo docker logs ticket-worker

Erwartet werden worker uid=0 und tickets=3. Die UID 0 ist Root. Die Geschäftsaufgabe funktioniert, verwendet aber mehr Rechte als nötig.

docker exec führt einen Untersuchungsbefehl in diesem laufenden Container aus. id -u gibt seine effektive numerische Benutzer-ID aus:

sudo docker exec ticket-worker id -u

Erwartet wird erneut 0. docker inspect liest die Konfiguration. --format wählt ein Feld aus; kopiere die Vorlage in Anführungszeichen exakt. .Config.User ist der konfigurierte Benutzer:

sudo docker inspect --format '{{.Config.User}}' ticket-worker

Erwartet wird root. Die Image-Einstellung und die Untersuchung des echten Prozesses stimmen überein. Eine Datei mit der Aufschrift „non-root“ würde diesen Nachweis nicht liefern.

Mit dem vorbereiteten Benutzer ohne Root-Rechte neu bauen

In diesem Schritt legst du die standardmäßige Anwendungsidentität des Images fest.

Das zwischengespeicherte Basisimage enthält bereits nobody mit der UID 65534. Weder das Anlegen eines Benutzers noch ein Passwort ist nötig. Öffne das Dockerfile:

nano Dockerfile

Ändere nur die Anweisung USER. Die vollständige Datei sollte so aussehen:

FROM desklite-base:1.37.0
WORKDIR /app
COPY worker.sh /app/worker.sh
USER nobody
CMD ["sh", "/app/worker.sh"]

Speichere mit Ctrl+O, Enter und beende den Editor mit Ctrl+X.

sudo docker build -t ticket-report:practice .

Der Tag bezeichnet jetzt das neu gebaute Image. Vorhandene Container wechseln nicht automatisch zu einem neu gebauten Image; der alte Root-Container ist weiterhin die alte Instanz. Im nächsten Schritt ersetzt du ihn und gibst der neuen Identität den erforderlichen Ausgabezugriff.

Den Container mit eingeschränkten Mounts ersetzen

In diesem Schritt kombinierst du die Identität ohne Root-Rechte mit funktionierenden Dateiberechtigungen.

docker rm entfernt einen Container. -f stoppt ihn zunächst, falls er läuft. Entferne ausschließlich den benannten Container dieser Übung:

sudo docker rm -f ticket-worker

Das Berichtsverzeichnis gehörte anfangs Root. Wie im Dateiberechtigungs-Lab benötigt ein Prozess ohne Root-Rechte Eigentümerschaft und Berechtigungen, um seine Ausgabe zu schreiben. chown -R ändert das Verzeichnis und seinen vorhandenen Bericht rekursiv auf die UID und GID 65534. Der Modus 750 gewährt dem Eigentümer vollen Zugriff ohne allgemeine Schreibrechte:

sudo chown -R 65534:65534 output
sudo chmod 750 output

Erstelle jetzt den Ersatz. Der Eingabe-Mount erhält readonly; der Ausgabe-Mount bleibt beschreibbar. Das verhindert Änderungen an der Eingabe und erlaubt zugleich die Erzeugung des Berichts:

sudo docker run -d --name ticket-worker --network none --mount type=bind,src=/home/labex/project/ticket-worker/input,dst=/data/input,readonly --mount type=bind,src=/home/labex/project/ticket-worker/output,dst=/data/output ticket-report:practice

Erwartet wird eine neue Container-ID. Das Image legt jetzt den Benutzer ohne Root-Rechte fest; eine Überschreibung auf Root zur Laufzeit ist nicht nötig.

Identität, Zugriff und Geschäftsausgabe prüfen

In diesem Schritt prüfst du alle drei Teile des Ergebnisses am Ersatzcontainer.

sudo docker exec ticket-worker id -u

Erwartet wird 65534. Prüfe die tatsächliche Ausgabe des Workers:

sudo docker logs ticket-worker

Erwartet werden worker uid=65534 und tickets=3. Lies die echte Ausgabedatei durch den Container:

sudo docker exec ticket-worker cat /data/output/report.txt

Erwartet wird tickets=3. Das beweist, dass die Verringerung der Rechte die Aufgabe nicht beeinträchtigt hat.

Untersuche die Mounts. Die Vorlage in Anführungszeichen wählt .Mounts aus und formatiert sie als JSON:

sudo docker inspect --format '{{json .Mounts}}' ticket-worker

Suche nach /data/input mit "RW":false und /data/output mit "RW":true. RW bedeutet Lesen/Schreiben. Dieselben Eingabebytes bleiben zum Lesen verfügbar, aber der Mount verweigert das Schreiben.

Versuche, eine harmlose Datei anzulegen, um diese Einschränkung zu testen. touch würde eine leere Datei erstellen, falls dies erlaubt wäre:

sudo docker exec ticket-worker touch /data/input/probe.txt

Erwartet werden ein Fehler Read-only file system und ein Exit-Status ungleich null. Diese eine Verweigerung ist beabsichtigt. Mache die Eingabe nicht beschreibbar, um sie zu beseitigen; der Bericht wurde bereits mit schreibgeschützter Eingabe erfolgreich erzeugt.

Bestätigen, dass der abschließende Worker noch läuft

In diesem Schritt lässt du den vorgesehenen Worker für die Untersuchung bereitstehen.

sudo docker ps

Der benannte Container ticket-worker sollte laufen. Bestätige den konfigurierten Benutzer und den Bericht erneut:

sudo docker inspect --format '{{.Config.User}}' ticket-worker
sudo docker exec ticket-worker cat /data/output/report.txt

Erwartet werden nobody und tickets=3. Die Kursprüfung vergleicht außerdem das laufende Image mit deinem Auslieferungstag, prüft die tatsächliche Worker-PID und testet die ursprüngliche Ein- und Ausgabe. Ein Dockerfile neu zu bauen, ohne den Container zu ersetzen, reicht nicht aus.

Diese Übung schränkt Anwendungsidentität und Dateizugriff ein. Sie macht den Zugriff auf den Docker-Daemon nicht unprivilegiert und begründet keine vollständige Richtlinie zur Container-Härtung im Produktivbetrieb.

Zusammenfassung

Du hast ein echtes Image mit einer Anwendungsidentität ohne Root-Rechte gebaut, dieses Image mit eingeschränktem Eingabezugriff und funktionierendem Ausgabezugriff ausgeführt und den tatsächlichen Prozess sowie den Bericht geprüft. Image-Konfiguration, Laufzeitidentität, Mount-Modi und funktionales Verhalten tragen gemeinsam zum Endzustand bei.