In diesem Artikel möchte ich sieben Möglichkeiten aufzeigen, um sichere und einfach zu wartende Docker-Container zu bauen. Natürlich gibt es noch mehr Optimierungsmöglichkeiten als die sieben, die ich hier beschreibe. Mit diesen hier aufgelisteten fährt man allerdings schon ziemlich gut.
Mit einem minimalen Basisimage wird automatisch eine geringere Angriffsfläche erreicht. Denn weniger Funktionen bedeuten automatisch auch eine verringerte Anzahl an Möglichkeiten, die ein Angreifer im Falle eines Durchbruchs hat.
Ist zum Beispiel kein curl
oder nc
installiert, so kann ein Angreifer zumindest mit diesen Tools keinen weiteren Schadcode nachladen.
Hier eignet sich vor allem ein Alpine Linux als Basisimage. Dieses ist im Vergleich zu Debian sehr klein. Im direkten Größenvergleich ist ein aktuelles Debian 12 Image 49 MB groß, während das Alpine Image nur 3,4 MB groß ist.
In der hier verlinkten Animation vergleiche ich die Größen der beiden Images. Wenn möglich, sollte also immer Alpine genutzt werden.
Beim Bauen des Images im Dockerfile sollte auch immer ein Update der bereits vorinstallierten Pakete erfolgen. Ganz besonders dann, wenn zur Beschleunigung des Buildvorgangs eigene Basisimages definiert werden.
In diesem Beispiel aktualisiere ich ein Alpine Image.
Standardmäßig gilt in der Informatik das Prinzip der geringsten erforderlichen Privilegien. Nach Installation der nötigen Komponenten sollte vom Root-User auf einen normalen Nutzer gewechselt werden. Es sollten nur die minimal notwendigen Berechtigungen vergeben werden, die für den Betrieb des Containers erforderlich sind.
Dies stellt im Falle eines erfolgreichen Angriffs auf die eigentliche Applikation sicher, dass eine weitere Hürde existiert, die durch den Angreifer erst noch überwunden werden muss. Zur Minimierung der Angriffsfläche sollte dies standardmäßig für alle Docker-Container implementiert werden.
Hier wird dieses Vorgehen anhand einer minimalen Python3-Applikation demonstriert:
FROM python:3.11-slimRUN useradd -m app_user # Anlage eines neuen UsersUSER app_user # Änderung des Kontextes auf neu erstellten UserCOPY . /appWORKDIR /appENTRYPOINT ["python3", "/app/my_app.py"]
Neben dem Wechsel des Benutzers ist es auch möglich, das Root-Filesystem in den Read-only-Modus zu versetzen. Dies stellt sicher, dass bereits bestehende Binaries im Container nicht mehr durch den User verändert werden können.
Hiermit erzeugen wir einen doppelten Boden zu den bereits reduzierten Rechten im vorherigen Teil. Wer mehr dazu lesen möchte, findet hier weitere Informationen: https://docs.datadoghq.com/security/default_rules/cis-docker-1.2.0-5.12/
Durch das explizite Setzen des Flags --read-only
wird das Root-Filesystem in den Read-only-Modus versetzt. Ein Schreiben ist nun auch mit Root-Rechten nicht mehr möglich.
Bekommt ein Angreifer über eine Lücke in der Applikation eine Shell, so ist es ihm nicht möglich, bestehende Binaries im Container zu manipulieren, selbst wenn er aus dem User-Kontext ausbrechen und Root-Rechte erlangen kann.
Umgebungsvariablen sollten nicht für sensible Informationen wie Passwörter oder API-Schlüssel verwendet werden, da sie in vielen Kontexten sichtbar sind. Beispielsweise können diese dann einfach über docker inspect
auf dem Hostsystem oder in Log-Dateien ausgelesen werden.
Im Optimalfall sollten Secrets-Management-Tools wie Docker Secrets, HashiCorp Vault oder ähnliche Lösungen genutzt werden, um die Sicherheit zu erhöhen. In der Praxis ist eine volle Integration dieser Tools allerdings schwierig. Besonders dann, wenn Legacy-Code oder eine sich schnell wandelnde Systemlandschaft im Spiel ist. Würde man diesen Ansatz konsequent umsetzen, so müsste die entsprechende Client-Komponente des Secret-Management-Tools in die Anwendung integriert werden.
Daher nutze ich bisher trotzdem Umgebungsvariablen. Mein bevorzugter Weg ist beim Einsatz von purem Docker die Ablage von Secrets in einem separierten Environment-File, welches ich dann von einem Secret-Management-Tool befülle.
Secrets sollten in jedem Fall ausgelagert werden und nicht mit dem Sourcecode oder Docker Image direkt ausgeliefert werden.
Durch den Einsatz von digitalen Signaturen ist es möglich, die Überprüfung der Integrität und Herkunft von Docker-Images zu gewährleisten. Dadurch wird die Lieferkette der Software abgesichert und die Ausführung von manipulierten Images verhindert.
Diese Technik nutzt beispielsweise Apple konsequent in seinem Betriebssystem. Die Ausführung von Anwendungen, die nicht durch ein Apple Developer Certificate unterschrieben wurden, wird standardmäßig blockiert und muss explizit durch den Nutzer erlaubt werden.
Ein Beispiel für einen erfolgreichen Angriff auf die Software-Lieferkette kann hier nachgelesen werden: https://www.heise.de/news/VoIP-Anbieter-3CX-Die-doppelte-Supply-Chain-Attacke-8974948.html
Mit Docker Content Trust gibt es ein ähnliches System, mit welchem Images digital signiert werden können. Das ausführende Host-System wird daraufhin so konfiguriert, dass es nur noch signierte Images ausführen darf.
Multi-Stage Builds sind eine gute Möglichkeit, um die Größe des Images zu reduzieren und unnötige Dateien und Abhängigkeiten in den Images zu vermeiden, in denen am Ende die Anwendung läuft. Der Build-Prozess eines Docker Images wird in mehrere Stufen aufgeteilt.
Im hier gezeigten Beispiel muss eine Anwendung mit einer ganz bestimmten Abhängigkeit gebaut werden. In diesem Fall ist es der ldap-client
für python3.9.
Da die Abhängigkeit selbst gebaut werden muss, wird ein zusätzlicher Layer eingefügt, in welchem der ldap-client
gebaut wird. Dieser Layer wird am Ende verworfen und nur die kompilierte Anwendung in das Applikations-Image kopiert.
Durch den Einsatz der hier aufgezeigten Methoden kann durch das einfache Anwenden von Regeln und Prinzipien bereits ein hoher Sicherheitsgrad erreicht werden. Natürlich ersetzen diese Prinzipien nicht die Einhaltung von Regeln für die Erstellung von sicherer Software. Sie können es aber einem Angreifer erheblich erschweren, ein System zu kompromittieren.
Netcup bietet virtuelle Root-Server für einen fairen Preis an.
Mit dem Gutschein 36nc17009374344 erhälst du 5€ auf deine erste Bestellung.
Hier bestellen und einen Rabatt erhalten.
Falls der Gutschein nicht mehr funktioniert, bitte in die Kommentare schreiben.
Quick Links