Container Security mit Docker

Docker ist das bekannteste Tool zur Container-Virtualisierung mit dem Unterschied, keine richtige Virtualisierung zu sein. Keine richtige Virtualisierung bedeutet keine vollständige Isolierung vom Host. Auf welche Security-Maßnahmen kommt es nun an, um Docker Images und Container sicher erstellen und betreiben zu können?


Docker verstehen - Ein kurzer Blick in die Tiefe

Docker basiert auf dem selben Verfahren wie der LXC-Container. Die Prozesse des Containers werden dabei auf dem Host-System mittels der Kernelfunktionen control groups (cgroups) und namespaces von den Host-Prozessen isoliert. Dies ist auch der Grund, weshalb Docker unter Windows über eine entsprechende Linux-VM betrieben wird. Die namespaces-Funktionalität ist nämlich ein Linux-Feature zur Trennung und Isolierung von Ressourcen unter Linux. Durch diese Art der Isolierung sind die Prozesse innerhalb des Container-namespaces nicht für die Prozesse des Hosts sichtbar. Dies gilt ebenso in entgegengesetzer Richtung. Die Verwaltung der Container selbst und somit die Zuordnung der Prozesse zu den jeweiligen namespaces erfolgt durch den Docker Daemon. Aufgrund der Notwendigkeit Prozesse entsprechend den namespaces zuordnen zu können, werden für den Docker Daemon stets höhere Rechte und somit die Ausführung des Daemons durch einen Admin-User gefordert.

Least Privilege

Mit dem Wissen, dass Docker Container auf dem namespace-Feature basieren und der Docker Daemon als Prozess unter Admin-Rechten läuft, ergibt sich bereits das erste Problem. Der Container und damit die Prozesse des Containers werden durch einen Prozess mit Admin-Rechten erzeugt. Dies hat zur Folge, dass die innerhalb des Containers gestarteten Prozesse auf dem Host mit Admin-Rechten ausgeführt werden. Aufgrund dieser Situation kann ein Ausbruch aus dem Container schwerwiegende Folgen für den Host bedeuten, da die Prozesse bereits Admin-Rechte besitzen. Für Produktivumgebungen stellt dies ein großes Risiko dar. Die Lösung hierfür beruht nun auf dem Grundprinzip Least Privilege der IT-Sicherheit und heißt Rootless Containers. Wie der Name bereits erahnen lässt, ermöglicht diese Funktionalität den Betrieb des Daemon und der zugehörigen Container-Prozesse ohne Admin-Rechte.

Rebuild und Redeployment statt Patch-Management

Die Fehlerkorrektur eines Containers in der Produktivumgebung mittels Patch-Management ist für sicherheitsrelevante Fehler wenig praktikabel. Die Idee hinter der Container-Virtualisierung basiert auf der Bereitstellung einer immer gleichen Umgebung für die gewählte Applikation. Der beste Weg die entdeckten Probleme des Containers zu lösen ist deshalb ein Rebuild des Images und ein Redeployment des Containers. Dieses Prinzip bietet einen weiteren wesentlichen Vorteil. Funktionalitäten und Tools zur manuellen Verwaltung von Containern, wie zum Beispiel die Shell und ein SSH-Zugang, können vom Container entfernt werden. All diese Tools werden für einen Betrieb meist grundsätzlich nicht benötigt. Somit kann man die Möglichkeiten eines Angreifers nach dem Eindringen in den Container weiter reduzieren.

Security by Design

Neben dem Betrieb der Container muss auch das zugehörige Image entsprechend sicher gestaltet werden. Die Erstellung eines durchdachten und sicheren Images kann dabei nach dem Prinzip Security by Design erfolgen. Das Dockerfile entspricht hierbei dem Design des Images und bietet diverse Maßnahmenpakete.

Die Grundlage für jedes Image sollten zunächst zwei wesentliche Anforderungen sein:

  • “Minimale Base-Images” - Statt umfangreicher Base-Images (z.B. Ubuntu) für den Betrieb einer einfachen Anwendung nur ein minimales Base-Image verwenden (z.B. Linux Alpine). Notwendige bzw. zusätzliche Software sollte dann per Hand nachinstalliert werden.
  • Signierte, vertrauenswürdige Base-Images aus vertrauenswürdigen Registries als Grundlage verwenden

Container mit non-root User starten

Eine Regel, die auch auf dem Prinzip Least-Privilege basiert, ist das Verwenden eines non-root Users innerhalb des Containers. Dies erfolgt über den Befehl USER. Basiert das Image bereits auf einem Base-Image mit einem bereits vordefinierten non-Root User - ein Beispiel wäre das Image node, welches den non-Root User node enthält - so kann dieser ebenso über den USER Befehl direkt gewählt werden.

# Beispiel für die vereinfachte Verwendung eines non-Root Users ohne spezielle Gruppen- und Userkonfigurationen
USER 1000

# Beispiel für die Verwendung eines bereits bestehenden Users
USER node

# Neue non-root Gruppe und non-Root User erstellen und der Gruppe zuweisen
RUN addgroup -g 1000 nonRoots \
    && adduser -u 1000 -G nonRoots -s /bin/sh -D nonRoot
	
USER nonRoot

Multi-Stage und distroless

Ein weiterer Schritt die Sicherheit mittels der Konfiguration des Dockerfiles zu erhöhen sind Multi-Stage und distroless Images. Bei der Ausführung der Applikation wird im Container beispielsweise die Software zum Build der ausführbaren Anwendung und der Quellcode des Projektes nicht benötigt. Diese Tools können viel mehr ein Sicherheitsrisiko darstellen. Ein Angreifer, der bereits Zugriff auf den Container hat, kann zum Beispiel einen Einblick in den Quellcode erhalten, diesen verändern und schlussendlich eine neue Version kompilieren. Die Lösung hierfür sind Multi-Stage-Dockerfiles. Hier kann zunächst die Applikation in der ersten Stage kompiliert und das finale Binary mit dem Image aus der zweiten Stage deployed werden. Hierzu das folgende Beispiel:

# Builder Image
FROM openjdk:11-jdk AS builder

WORKDIR /project/
RUN mvn clean compile

# Image for Deployment
FROM alpine:latest  
RUN apk add openjdk8-jre-base && apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /project/target/src/binary.jar .
CMD ["java", "-jar binary.jar"]  

Die Möglichkeit distroless Images zu erstellen, reduziert die Angriffsfläche nochmals um einen wesentlichen Faktor. Nun ist nur noch die auszuführende Binary vorhanden. Ein Beispiel für ein entsprechendes Dockerfile zeigt der folgende Ausschnitt:

# Builder Image
...

# Image for Deployment mittels der distroless Google Container Images
FROM gcr.io/distroless/java:11
WORKDIR /app
COPY --from=builder /project/target/src/binary.jar .
CMD ["binary.jar"]

Ein kleiner Hinweis: Die Verwendung von Multi-Stage und distroless Images bietet neben der Verbesserung der Sicherheit zusätzlich eine Performance-Verbesserung. Die resultierenden Images haben nun nur noch einen Bruchteil der Speichergröße eines konventionellen Images. Sie ermöglichen einen ressourcensparenden Betrieb sowie ein schnelleres Deployment aufgrund der geringeren Datenmenge.

COPY und ADD

Ein weiterer Punkt, der ebenso zu Problemen führen kann, ist die Verwendung des Befehls ADD beim Kopieren von Dateien in das Image. ADD bietet die Möglichkeit der Angabe einer URL, beispielsweise für den Download einer Datei. Zudem können aber auch Archive mittels ADD direkt beim Kopieren in das Image entpackt werden. Wird bei der Verwendung nicht darauf geachtet welche Dateien in den Container gepackt werden, bestehen sicherheitsrelevante Anfälligkeiten für Schwachstellen wie beispielsweise ZIP-Bombs. Somit sollte beim Kopieren der Befehl ADD nur mit Bedacht verwendet wird. Der Befehl COPY ist stattdessen vorzugsweise zu verwenden.

Zusammenfassung

Zusammenfassend erhöhen folgende Punkte die Sicherheit des Betriebs von Containern um ein wesentliches:

  • Rootless Container auf Produktivumgebungen
  • Statt den Container zu patchen ein Rebuild des Images mit Redeployment des Containers bevorzugen
  • Minimales, kleines Base-Image inkl. der manuellen Installation der benötigten Software-Tools
  • Signierte, vertrauenswürdige Images aus vertrauenswürdigen Registries
  • Container mit non-root Usern starten
  • Multi-Stage und distroless Images erstellen
  • ADD nur wenn wirklich notwendig und die Quelle geprüft ist, sonst COPY für das Kopieren der Dateien in den Container bevorzugen
  • Auf vertrauenswürdige, signierte Base-Images zurückgreifen

Neben den genannten Punkten, können noch weitere nicht genannte Maßnahmen zur Verbesserung der Sicherheit umgesetzt werden (z.B. Meta-Labels und Image-Tags, siehe Snyk Best Practices). Ein entsprechendes Secret-Management im Dockerfile für den Build des Images sowie regelmäßige Updates des Host und des Container-Images sind ebenso obligatorisch und gehören unabhängig von der Container-Virtualisierung zu den Hauptaspekten eines sicheren Softwareentwicklungsprozesses. Auch das Log-Management ist stets ein wichtiger Faktor für einen sicheren Betrieb und sollte ebenso nicht vergessen werden. Kontinuierliche Scans der Dockerfiles und Container-Images sind empfehlenswert. Tools wie Clair, OpenSCAP, Docker Bench Security und Docker Scan von Snyk sind hierfür geeignet.

Abschließend gilt für die Container-Virtualisierung was in jedem Bereich der Softwareentwicklung gilt: IT-Security ist ein kontinuierlicher Prozess, welcher stetig neu justiert und verbessert werden sollte.

Referenzen