HomeAboutContact

7 Wege zu sicheren Docker Containern

Von Yannik Korzikowski
Veröffentlich in Software Entwicklung
09. October 2024
3 min Lesezeit
🇩🇪
7 Wege zu sicheren Docker Containern

Inhalt

01
Minimales Basisimage nutzen
02
Image aktuell halten
03
Principle of Least Privilege
04
Zusatz Principle of Least Privilege: Read-Only Filesystem
05
Umgebungsvariablen nutzen für Secrets
06
Docker Content Trust nutzen
07
Multi-Stage Builds nutzen
08
Fazit

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.

Minimales Basisimage nutzen

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.

Image aktuell halten

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.

Principle of Least Privilege

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-slim
RUN useradd -m app_user # Anlage eines neuen Users
USER app_user # Änderung des Kontextes auf neu erstellten User
COPY . /app
WORKDIR /app
ENTRYPOINT ["python3", "/app/my_app.py"]

Zusatz Principle of Least Privilege: Read-Only Filesystem

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 nutzen für Secrets

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.

Docker Content Trust nutzen

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 nutzen

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.

Fazit

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.

Photo by https://pixabay.com/de/photos/spornschildkr%C3%B6te-schildkr%C3%B6te-natur-8994997/


Affiliate

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.


Tags

dockersecurity

Share


Vorheriger Artikel
Custom Ansible GPT mit ChatGPT
Yannik Korzikowski

Yannik Korzikowski

Cloud Architect

Auch interessant

7 Best Practices for Creating Secure and Maintainable Docker Containers
7 Best Practices for Creating Secure and Maintainable Docker Containers
October 11, 2024
3 min
🇬🇧

Quick Links

AboutContact me

Social Media