Best Practices für das Härten von Docker-Images und Reduzierung der Angriffsfläche
Härten Sie Docker-Images durch die Verwendung von Nicht-Root-Benutzern, kleineren Basen, Multi-Stage-Builds, sicherem Umgang mit Geheimnissen und Schwachstellenscans.
Best Practices für das Härten von Docker-Images und Reduzierung der Angriffsfläche
Das Härten von Docker-Images beginnt mit einer einfachen Frage: Was befindet sich in diesem Image, das Ihre App nicht wirklich benötigt? Zusätzliche Benutzer, Shells, Paketmanager, Build-Tools und durchgesickerte Geheimnisse erhöhen alle den Schaden, den ein kompromittierter Container anrichten kann.
Wenden Sie diese Praktiken an, wenn Sie ein Dockerfile schreiben oder überprüfen, insbesondere bevor ein Image ein gemeinsames Registry oder Produktionscluster erreicht.
Ausführen von Containern als Nicht-Root-Benutzer
Eines der grundlegendsten Sicherheitsprinzipien ist das Prinzip der geringsten Privilegien. Standardmäßig laufen Prozesse innerhalb eines Docker-Containers als Root-Benutzer. Dies gewährt ihnen umfangreiche Berechtigungen, die von Angreifern ausgenutzt werden können, wenn der Container kompromittiert wird. Wenn Sie Ihre Anwendung als Nicht-Root-Benutzer ausführen, reduzieren Sie den potenziellen Schaden, den ein Angreifer innerhalb des Containers anrichten kann, drastisch.
Erstellen eines Nicht-Root-Benutzers
Sie können einen neuen Benutzer und eine neue Gruppe in Ihrem Dockerfile erstellen und dann zu diesem Benutzer wechseln, bevor Sie Ihre Anwendung ausführen.
# Verwenden Sie eine offizielle Python-Laufzeit als übergeordnetes Image
FROM python:3.9-slim
# Legen Sie das Arbeitsverzeichnis fest
WORKDIR /app
# Kopieren Sie den aktuellen Verzeichnisinhalt in den Container unter /app
COPY . /app
# Installieren Sie alle in requirements.txt angegebenen benötigten Pakete
RUN pip install --no-cache-dir -r requirements.txt
# Erstellen Sie einen Nicht-Root-Benutzer und eine Gruppe
RUN addgroup --system --gid 1001 appgroup && \
adduser --system --uid 1001 --ingroup appgroup appuser
# Wechseln Sie zum Nicht-Root-Benutzer
USER appuser
# Machen Sie Port 80 für die Welt außerhalb dieses Containers verfügbar
EXPOSE 80
# Definieren Sie die Umgebungsvariable
ENV NAME World
# Führen Sie app.py aus, wenn der Container startet
CMD ["python", "app.py"]
Überlegungen zu Nicht-Root-Benutzern
- Berechtigungen: Stellen Sie sicher, dass der Nicht-Root-Benutzer die erforderlichen Lese- und Schreibberechtigungen für Verzeichnisse und Dateien hat, die von Ihrer Anwendung benötigt werden. Möglicherweise müssen Sie
chownverwenden, um die Eigentümerschaft entsprechend festzulegen. - Port-Bindung: Nicht-Root-Benutzer können normalerweise nur an Ports über 1024 binden. Wenn Ihre Anwendung an einen privilegierten Port (z. B. 80 oder 443) binden muss, sollten Sie die Verwendung eines Reverse-Proxys (wie Nginx oder Traefik) in Betracht ziehen, der auf dem Host oder in einem anderen Container mit entsprechenden Berechtigungen läuft, oder Linux-Fähigkeiten konfigurieren.
Minimieren Sie installierte Pakete und Abhängigkeiten
Jedes in Ihrem Docker-Image installierte Paket erhöht dessen Größe und, was noch wichtiger ist, seine Angriffsfläche. Jedes Paket kann seine eigenen Schwachstellen aufweisen, die Angreifer ausnutzen können. Daher ist es entscheidend, nur das absolut Notwendige einzuschließen.
Best Practices für die Paketverwaltung:
- Verwenden Sie minimale Basis-Images: Ziehen Sie
slim, distroless oder Alpine-basierte Images in Betracht, wenn sie zu Ihrer Laufzeit passen. Kleinere Images enthalten tendenziell weniger Pakete, aber testen Sie immer die Kompatibilität, da Alpine musl libc verwendet und sich anders verhalten kann als Debian- oder Ubuntu-Images. - Bereinigen Sie nach der Installation: Bereinigen Sie nach der Installation von Paketen den Cache des Paketmanagers oder temporäre Dateien. Dies reduziert nicht nur die Image-Größe, sondern entfernt auch potenzielle Staging-Bereiche für Angreifer.
# Beispiel für Debian/Ubuntu-basierte Images RUN apt-get update && apt-get install -y --no-install-recommends some-package && \ rm -rf /var/lib/apt/lists/* # Beispiel für Alpine-basierte Images RUN apk add --no-cache some-package - Multi-Stage-Builds: Dies ist eine leistungsstarke Technik, um Ihr endgültiges Image schlank zu halten. Sie verwenden eine Stufe, um Ihre Anwendung zu erstellen (Installation von Build-Tools, Compilern usw.), und eine zweite, saubere Stufe, um nur die erforderlichen Artefakte aus der Build-Stufe zu kopieren. Dies verhindert, dass Build-Abhängigkeiten in Ihrem Produktions-Image landen.
# --- Build-Stufe --- FROM golang:1.18-alpine AS builder WORKDIR /app COPY . . RUN go build -o myapp # --- Produktionsstufe --- FROM alpine:latest WORKDIR /app COPY --from=builder /app/myapp . CMD ["./myapp"] - Aktualisieren Sie regelmäßig Abhängigkeiten: Halten Sie Ihre Anwendungsabhängigkeiten und Basis-Images auf dem neuesten Stand, um Sicherheitspatches zu integrieren.
Implementieren Sie robuste Health Checks
Health Checks sind entscheidend für die Überwachung des Status Ihrer Container. Docker kann diese Checks verwenden, um festzustellen, ob ein Container korrekt läuft, und um nicht gesunde Container automatisch neu zu starten oder zu entfernen. Ein gut definierter Health Check hilft sicherzustellen, dass Ihre Anwendung nicht nur läuft, sondern auch reaktionsfähig ist und wie erwartet funktioniert.
Definieren von Health Checks
Eine HEALTHCHECK-Anweisung in Ihrem Dockerfile gibt einen Befehl an, den Docker regelmäßig innerhalb des Containers ausführt, um seine Gesundheit zu testen. Wenn der Befehl mit einem Nicht-Null-Status beendet wird, gilt der Container als ungesund.
# Beispiel für eine Webanwendung
FROM nginx:latest
# ... andere Anweisungen ...
# Überprüfen Sie, ob der Nginx-Prozess läuft und Port 80 überwacht
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:80/ || exit 1
# ... andere Anweisungen ...
Best Practices für Health Checks:
- Halten Sie sie einfach: Der Health Check-Befehl sollte leichtgewichtig und schnell ausführbar sein. Vermeiden Sie komplexe Logik, die den Check verlangsamen oder eigene Fehlerquellen einführen könnte.
- Testen Sie die Kernfunktionalität: Der Check sollte idealerweise die Kernfunktionalität Ihrer Anwendung testen, nicht nur, ob ein Prozess läuft. Für einen Webserver könnte dies bedeuten, zu überprüfen, ob er auf eine grundlegende HTTP-Anfrage antworten kann.
- Konfigurieren Sie
start-period: Verwenden Sie für Anwendungen, die Zeit zum Initialisieren benötigen, die Optionstart-period, um ihnen Zeit zum Starten zu geben, bevor Health Checks fehlschlagen.
Sichere Verwaltung von Geheimnissen und sensiblen Daten
Betten Sie niemals Geheimnisse wie API-Schlüssel, Passwörter oder Zertifikate direkt in Ihr Dockerfile oder Image ein. Diese Geheimnisse werden Teil der Image-Ebene und sind leicht auffindbar. Verwenden Sie stattdessen Docker Secrets oder Umgebungsvariablen, die von Ihrer Orchestrierungsplattform (wie Kubernetes oder Docker Swarm) für sensible Informationen verwaltet werden.
Docker Secrets im Swarm-Modus
Docker Swarm bietet einen nativen Mechanismus zur Verwaltung von Geheimnissen. Sie können Geheimnisse erstellen und sie als Dateien in Container einbinden.
# Erstellen Sie ein Geheimnis
docker secret create my_api_key api_key.txt
# Stellen Sie einen Dienst mit dem Geheimnis bereit
docker service create --secret my_api_key my_web_app
Umgebungsvariablen mit Vorsicht
Obwohl Umgebungsvariablen praktisch sind, sind sie auch sichtbar, wenn ein laufender Container inspiziert wird (docker inspect). Verwenden Sie sie für nicht sensible Konfigurationsdaten. Für sensible Daten werden Docker Secrets oder externe Geheimnisverwaltungssysteme bevorzugt.
Verwenden Sie spezifische Image-Tags
Wenn Sie in Ihrem Dockerfile auf Basis-Images oder andere Images verweisen (z. B. FROM ubuntu:latest), verwenden Sie immer spezifische Versionstags anstelle von latest. Die Verwendung von latest kann zu unvorhersehbaren Builds führen, da sich das latest-Tag im Laufe der Zeit ändern kann, was möglicherweise bahnbrechende Änderungen oder sogar Sicherheitslücken ohne Ihr Wissen einführt.
# Vermeiden Sie dies:
# FROM ubuntu:latest
# Bevorzugen Sie dies:
FROM ubuntu:22.04
Scannen Sie Images auf Schwachstellen
Scannen Sie Ihre Docker-Images regelmäßig auf bekannte Schwachstellen. Mehrere Tools können Ihnen dabei helfen, sowohl in Ihrer CI/CD-Pipeline als auch in Ihrem Registry.
Beliebte Scanning-Tools
- Trivy: Ein einfacher und umfassender Schwachstellenscanner für Container. Er scannt Betriebssystempakete und Anwendungsabhängigkeiten.
trivy image your-image-name:tag - Clair: Ein Open-Source-Tool zur statischen Analyse zur Erkennung von Schwachstellen in Container-Images.
- Docker Scout: Ein Dienst von Docker, der Container-Images auf Schwachstellen analysiert und Empfehlungen gibt.
Die Integration dieser Scans in Ihren Build-Prozess stellt sicher, dass Sie sich potenzieller Sicherheitsprobleme bewusst sind und diese beheben können, bevor Sie Ihre Images bereitstellen.
Verstehen Sie Image-Ebenen
Docker-Images werden in Ebenen erstellt. Wenn Sie eine Änderung an Ihrem Dockerfile vornehmen, wird eine neue Ebene erstellt. Wenn Sie verstehen, wie Ebenen funktionieren, können Sie Ihr Dockerfile sowohl in Bezug auf Größe als auch Sicherheit optimieren. Platzieren Sie Anweisungen, die sich weniger häufig ändern (wie das Installieren von Basispaketen), früher im Dockerfile und Anweisungen, die sich häufiger ändern (wie das Kopieren von Anwendungscode), später. Dies nutzt den Docker-Build-Cache effektiv und kann Builds beschleunigen.
Noch wichtiger für die Sicherheit: Sensible Informationen oder versehentliche Offenlegungen in früheren Ebenen können bestehen bleiben. Stellen Sie sicher, dass alle sensiblen Dateien oder Befehle so behandelt werden, dass sie nicht in den endgültigen Image-Ebenen verbleiben, wenn sie nicht mehr benötigt werden.
Machen Sie das Härten zur Routine
Beginnen Sie mit den Dockerfiles, die in die Produktion gehen. Entfernen Sie Build-Tools aus endgültigen Images, führen Sie sie als Nicht-Root-Benutzer aus, fixieren Sie Basis-Images und scannen Sie jeden Build. Behandeln Sie dann das Härten von Images als Teil der normalen Code-Überprüfung, nicht als einmalige Bereinigung.