Docker dient in erster Linie dazu Anwendungen (meist Server-Anwendungen) einzukapseln und vom Rest des Betriebssystems zu entkoppeln. Dabei wird auf den Linux Kernel vom Host-System zurückgegriffen und mittels cgroups und namespaces die Möglichkeiten des Docker-Containers eingeschränkt.
Container sind dabei kein Mittel um Anwendungen sicherer zu machen, sondern dienen in erster Linie dazu skalierbare Dienste bereit zu stellen. Sie haben jedoch den Vorteil, dass Anwendungen vom Rest des Systems entkoppelt sind, mehr dazu aber später.
Das Prinzip von Containern
Ein Container ist sehr grob gesagt eine leichtgewichtige VM (virtual machine). Er enthält ebenfalls ein virtuelles Betriebssystem besitzt jedoch eine andere Virtualisierungsebene als eine VM.
Eine VM basiert auf dem Hypervisor Prinzip. Der Hypervisor ist quasi ein Adapter von entweder Hardware auf VM oder Betriebssystem (host) auf VM.
Benutzt er direkt die Hardware, so ist er ein Typ-1 Hypervisor und besitzt eine höhere Performance gegenüber einem Typ-2 Hypervisor.
Ein Typ-2 Hypervisor (z.B. VirtualBox oder VMware) sitzt direkt auf einem existierenden Betriebssystem und benutzt dessen Treiber um mit der Hardware zu arbeiten. Die Performance und Sicherheit ist hierbei etwas geringer, ist aber weniger abhängig von der Hardware.
Container sitzen direkt auf dem Betriebssystem vom host und haben keinen Hypervisor, der zwischen OS und Container liegt. Dadurch erhöht sich die Performance auch weil mehrere Container Ressourcen teilen und diese nicht mehrfach gespeichert werden müssen.
Um Container zu ermöglichen muss dieser stark mit dem Betriebssystem interagieren. Hierzu dienen cgroups und namespaces.
cgroups
Eine cgroup (control group) ist im Linux Kernel ein Modul welches Ressourcen verwaltet und Prozesse in dessen Gebraucht beschränken kann. Das ist auch schon der wesentliche Grund warum cgroups für Docker so wichtig sind: Sie beschränken den Gebrauch von Ressourcen. So kann man beispielsweise den Zugriff auf eine kleine Menge von Ordnern beschränken, was der Sicherheit zugute kommt. Auch kann man die Ressourcen bei der Benutzung mehrerer Container auf einem host besser verteilen.
namespace
Ein namespace im Linux Kernel ist – wie der Name schon sagt – ein Namensraum in dem man global verwendete Bezeichner wiederverwenden kann. Das Prinzip von namespaces gibt es z.B. auch in Sprachen wie C++: So gibt es global eine Funktion a()
und einen namespace N
. Im namespace N
gibt es wieder eine Funktion a()
, die jedoch mit der globalen Funktion nicht in Konflikt steht, da man mittels a()
die globale und mittels N::a()
die Funktion im namespace N
erreicht werden kann.
Auch hier ist es für Docker wichtig einen eigenen namespace zu besitzen, da somit die Prozesse innerhalb des Containers von denen außerhalb entkoppelt sind und es keine Konflikte geben kann. Auch ist es nicht möglich in den namespace des parent-Prozesses zu wechseln, somit kann ein Prozess im Container nicht in einen namespace außerhalb wechseln, was wiederum der Sicherheit zugute kommt.
Wie Docker funktioniert
Docker ist ein Dienst, der cgroups und namespaces des Linux-Kernels benutzt um Container zu starten. Zunächst sollte geklärt werden was ein Image und was ein Container ist.
Ein Image ist ein verpacktes virtuelles Betriebssystem, welches die Dateien enthält, die auf dem host laufen sollen. Beispielsweise kann man sich ein Image vom g0Ch@ Server bauen (einem von mir entwickelten simplen Server geschrieben in golang). Im Image ist dann der Server und die go runtime enthalten, die man braucht um den Server zu compilieren und starten zu können. Alles andere wird vom host System geliefert.
Es gibt neben diversen Anwendungen auch ganze Betriebssysteme als Image.
Ein Container ist eine Instanz eines Images. Vergleicht man Docker mit einem Softwaresystem wäre ein image eine Klasse und ein Container ein Objekt der Klasse, eben eine Instanz. Man kann somit verschiedene Container des selben Images laufen lassen.
Durch cgroups und namespaces beeinflussen sich die Container nicht gegenseitig, denn jeder Container besitzt eigens zugeteilte Ressourcen und kennt keine anderen Container.
Sicherheit von Containern
VMs sind verhältnismäßig sicher (vor allem Typ-1 VMs), da host und das Betriebssystem auf der VM stark voneinander entkoppelt sind. Container jedoch teilen sich Ressourcen und haben direkten Zugriff auf Teile vom host. Auch wird ein Container standardmäßig mit root-Rechten ausgeführt, da sonst eine so starke Verbindung zum Kernel nicht möglich wäre. Dies erhöht jedoch das Risiko bei der Verwendung von Containern. Es ist aber mit dem Parameter --userns-remap
möglich die User ID 0 (normalerweise root-User) auf einen anderen Benutzer zeigen zu lassen.
Seit der Einbindung von namespaces ist die Hürde ein host System zu übernehmen jedoch sehr groß geworden und somit bieten Container eine erhöhte Sicherheit beim betreiben von Servern. Auch wenn ein Container mit root Rechten gestartet wird, so besitzen Prozesse in ihm keine root Rechte auf dem host System. Zusätzlich kann man mittels --drop-cap
Rechte „fallen lassen“ um weitere Einschränkungen vorzunehmen.
Ein Container besitzt zwar intern die User ID 0 (also die root-User ID), jedoch sind die Möglichkeiten für den Container sehr begrenzt. Man kann zum Beispiel mittels Volumes den Zugriff auf Unterordner beschränken, so gibt es keine Möglichkeit auf übergeordnete Ordner des host Systems zugreifen zu können. Ermöglicht wird das wieder durch die cgroups, die dieses Ressourcen-Management bieten.
Privilege elevation durch Benutzer der Gruppe docker
Unter Umständen kann es dennoch ein Sicherheitsrisiko geben, welches sich jedoch zunächst ausschließlich auf dem host System befindet. Und zwar können Nutzer ohne root-Rechte, die jedoch in der Gruppe docker
sind, Container starten und den docker daemon benutzen. Dies wiederum birgt ein Risiko, da eine privilege elevation vom normalen Benutzer zum Benutzer mit root-Rechten möglich ist.
Dabei startet man einen Container, der intern mit root Rechten läuft. Erlaubt man dem Container Zugriff auf z.B. das home-Verzeichnis des Nutzers kann man sich damit root-Rechte beschaffen:
[alice@docker] cp /bin/sh /home/alice
[alice@docker] docker run –rm –v /home/alice:/host busybox /bin/sh –c “chwon root.root /host/sh && chmod a+s /host/sh”
[alice@docker] ./sh
[root@docker] whoami
root
DockerHub mit Vorsicht genießen
Auch wenn Docker als Virtualisierungssystem relativ sicher ist, so liegt die Gefahr oftmals woanders. DockerHub bietet viele vorgefertigte Container für verschiedenste Anwendungen an, die man über den docker daemon herunterladen und starten kann. Diese Container besitzen aber oft Sicherheitslücken, die das ausbrechen aus dem Container erlauben, wie ein Beitrag zur blackhat Europe 2015 zeigte. Rund 30% aller Container und ganze 90% aller als offiziell markierten Container besaßen Sicherheitslücken, die man genau für solche Zwecke ausnutzen kann.
Fazit
Wie Eingangs erwähnt ist Docker kein Dienst um angreifbare Anwendungen nachträglich sicher(er) zu machen. Doch trotz der Sicherheitslücken und lokalen Probleme mit den Rechten ist Docker ein gutes Mittel um Services zu abstrahieren um vom Server ohne große Performance Verluste zu entkoppeln. Man sollte jedoch nicht auf die Sicherheit externer Container vertrauen, sondern sich im Vorfeld informieren und Images ggf. selber erstellen.
Hey Stephan, ja das solltest du machen! Der Angreifer hat es dadurch ein wenig schwerer auf dein Host System zu kommen!
Gruß
Hi ich wollte dir sagen, das es wirklich ein Interessanter Beitrag ist. Ich selbst betreibe eine Webseite und bin am überlegen, ob ich Docker auf meinem Webserver installieren soll? Was denkst du? Wäre es für einen sagen wir mal Anfänger im Docker was?
Gruß
Stephan