Blog
Der Vertrauensirrtum: Wie unser Multi-Tenant-Docker-Setup Daten preisgab (und wie wir es behoben haben)
Erfahren Sie, wie das naive Docker-Setup eines Teams zu einem Cross-Tenant-Datenleck führte und welche geschichtete Isolationsstrategie es verhinderte.
Zusammenfassung
Docker-Container sind standardmäßig nicht isoliert – sie teilen sich den Host-Kernel, und ohne bewusste Konfiguration können sich die Mandanten gegenseitig stören. Dieser Artikel beschreibt ein reales Szenario, bei dem ein Multi-Tenant-Hosting-Anbieter feststellte, dass Client-Container aufgrund gemeinsam genutzter Netzwerke und schwacher Sicherheitsstandards auf die Datenbanken anderer zugreifen konnten. Wir zeigen die schrittweisen Änderungen, die den Verstoß behoben haben: pro Mandant benutzerdefinierte Netzwerke, Nicht-Root-Benutzer, entfernte Capabilities, schreibgeschützte Dateisysteme und Seccomp-Profile. Eine verbreitete Annahme ist, dass Container von Natur aus eine starke Isolation bieten; wir stellen dies in Frage, indem wir erklären, warum VMs immer noch eine härtere Grenze bieten und wann ein hybrider Ansatz in Betracht gezogen werden sollte. Die Schlussfolgerung bekräftigt, dass Isolation eine geschichtete Übung ist, kein einzelnes Häkchen.
Der Vorfall: Wenn Container zu viel reden
Sie haben Docker auf einem einzelnen Host eingerichtet, um mehrere Client-Websites zu betreiben. Jeder Client hat seinen eigenen Container – eine saubere, isolierte Umgebung, oder? Das dachten wir auch. Bis eine routinemäßige Sicherheitsüberprüfung ergab, dass der Container von Client A den MySQL-Socket von Client B auf demselben Host auslas. Sie nutzten das standardmäßige bridge-Netzwerk. Schlimmer noch: Die Container liefen als Root, sodass ein Angreifer, der einen kompromittierte, den Docker-Socket des Hosts oder das Dateisystem eines anderen Containers manipulieren konnte. Der Verstoß war kein ausgeklügelter Exploit; es war eine grundlegende Fehlkonfiguration. Daten wurden preisgegeben. Vertrauen war zerstört.
Das Fehlerszenario ist nicht ungewöhnlich. Viele Teams gehen davon aus, dass Docker-Namespaces und Cgroups automatisch Mandanten abschotten, unterschätzen aber, wie viele Schlupflöder standardmäßig offen bleiben. Standard-Bridge-Netzwerke bieten keine Netzwerksolation zwischen Containern. Wenn Container als Root ausgeführt werden, haben sie mehr Macht als nötig. Und ohne explizite Ressourcenlimits kann ein lauter Nachbar anderen CPU oder Speicher entziehen.
Schritt 1: Kein gemeinsames Netzwerk mehr
Unser erster Fix bestand darin, jedem Mandanten sein eigenes benutzerdefiniertes Docker-Netzwerk zu geben. Dies verhindert, dass Container einander erreichen, es sei denn, Sie verbinden sie explizit. Wir erstellten ein Skript, das für jeden Mandanten ein dediziertes Netzwerk aufbaut und seinen Anwendungscontainer daran anhängt. Der Datenbankcontainer lebt im selben Mandantennetzwerk, aber wir fügten auch ein internes Netzwerk nur für die Kommunikation innerhalb des Mandanten hinzu. Kein Cross-Tenant-Schnüffeln mehr.
Wir isolierten die Datenbanken auch, indem wir sie in separaten Containern im selben Mandantennetzwerk mit getrennten Datenvolumes betrieben. So konnte selbst ein Angreifer, der in den App-Container eindrang, keinen Datenbankverkehr von anderen Mandanten abgreifen.
Für einen tieferen Einblick in Netzwerkisolutionsstrategien siehe die Eine praktische Docker-Isolations-Checkliste für Multi-Tenant-Hosting.
Schritt 2: Überflüssige Privilegien entfernen
Standardmäßig laufen Docker-Container mit einer begrenzten Anzahl von Linux-Capabilities, haben aber immer noch mehr, als die meisten Anwendungen benötigen. Unsere Container liefen als Root, was Prozessen im Inneren Aktionen wie das Einhängen von Dateisystemen oder das Ändern von Kernelparametern ermöglichte. Wir stellten die Anwendung auf einen Nicht-Root-Benutzer im Container um (mit der USER-Direktive im Dockerfile) und entfernten alle Capabilities bis auf die absolut notwendigen. Für eine typische Web-App könnte das nur NET_BIND_SERVICE (zum Binden an Ports unter 1024) und CHOWN (zum Schreiben in Verzeichnisse) sein. Wir fügten auch --security-opt no-new-privileges hinzu, um Privilegieneskalation zu verhindern.
Dieser Schritt allein beseitigte viele gängige Container-Escape-Vektoren. Ein Angreifer, der den Webserver kompromittiert, kann keine Pakete installieren, Systembinärdateien ändern oder auf den Docker-Socket des Hosts zugreifen, da dem Prozess die Capabilities CAP_SYS_ADMIN oder CAP_DAC_OVERRIDE fehlen.
Schritt 3: Das Dateisystem sperren
Beschreibbare Dateisysteme sind eine häufige Angriffsfläche. Wir machten das Root-Dateisystem für alle Container schreibgeschützt (--read-only) und mounteten temporäre Dateisysteme (tmpfs) für Verzeichnisse, die Schreibzugriff benötigen, wie /tmp und das Cache-Verzeichnis der Anwendung. Dies verhindert, dass ein Angreifer Anwendungscode ändert oder schädliche Binärdateien persistent ablegt.
Zusätzlich verwendeten wir DOCKERs --mount-Option, um sensible Verzeichnisse wie den Docker-Socket nur dann bind-mounten, wenn es unbedingt nötig ist – und nie in Produktionscontainern. Das Prinzip: Wenn der Container nicht in einen Pfad schreiben muss, machen Sie ihn schreibgeschützt.
Schritt 4: Seccomp- und AppArmor-Profile anwenden
Standard-Seccomp-Profile blockieren bereits viele gefährliche Syscalls, aber wir haben sie weiter angepasst, um nur die Syscalls zuzulassen, die unsere Anwendung tatsächlich benötigt. Das ist ein Kompromiss, da es ein Profiling der Anwendung erfordert. Ein einfacherer Ansatz ist die Verwendung des Docker-Standard-Seccomp-Profils und das Hinzufügen von --security-opt seccomp=path/to/profile.json, falls strengere Regeln erforderlich sind. Ähnlich können AppArmor-Profile Container-Prozesse auf bestimmte Dateipfade und Capabilities beschränken. Wir aktivierten AppArmor und verwendeten ein benutzerdefiniertes Profil, das den Zugriff nur auf die Datenverzeichnisse der Anwendung beschränkte.
Eine umfassende Anleitung zu diesen Härtungsschritten finden Sie unter Härtung von Docker-Containern für Multi-Tenant-Hosting: Eine Schritt-für-Schritt-Isolationsanleitung.
Die gegensätzliche Ansicht: Manchmal braucht man VMs
Egal wie gehärtet, Container teilen sich den Kernel des Hosts. Eine Kernel-Schwachstelle kann die gesamte Isolation auf einmal aufheben. Deshalb betreiben viele sicherheitsbewusste Plattformen Container in leichten VMs – jeder Mandant bekommt seinen eigenen Kernel. Dies verursacht Overhead, bietet aber eine Grenze auf Hardware-Ebene, die Container allein nicht schaffen. Wenn Ihre Mandanten Kreditkartendaten oder Gesundheitsakten verarbeiten, könnte ein hybrider Ansatz (Container in VMs) die richtige Wahl sein. Gehen Sie nicht davon aus, dass Containerisolation für Ihr Bedrohungsmodell ausreicht; bewerten Sie die Sensibilität der Daten und die regulatorischen Anforderungen.
Für einen tieferen Vergleich der Isolationsstufen lesen Sie Entwurf einer Multi-Tenant-Docker-Architektur: Wahl der richtigen Isolationsstufe.
Fazit: Isolation ist ein Stapel, kein Schalter
Der Fix war keine einzelne Änderung – es war eine Schichtung: Netzwerksolation, beschränkte Privilegien, schreibgeschützte Dateisysteme und Syscall-Filterung. Selbst dann akzeptierten wir, dass eine perfekte Isolation mit Shared-Kernel-Containern unmöglich ist. Für unsere sicherheitskritischsten Mandanten haben wir sie auf dedizierte Hosts verschoben. Die Lektion: Vertrauen Sie keiner Voreinstellung. Überprüfen Sie Ihr Docker-Setup, als ob ein Verstoß bereits passiert wäre. Die Zeit zum Absichern ist vor dem Leck, nicht danach.


