Die Thematik verdeutlichen wir anhand eines Beispiels. Dazu starten wir eine Anwendung in einem Docker Container.
Diese läuft innerhalb des Containers auf dem Port 8080 und soll auf dem Docker Host unter 9090 erreichbar sein. Wir weichen in
dem Beispiel vom Standardport 8080 des Hosts ab, damit man die Ports von Host und Container in unserem Beispiel besser
nachvollziehen kann. Der Start erfolgt über den nachfolgenden Befehl:
docker run -p 9090:8080
Nun können wir mit dem nachfolgenden Kommando die laufenden Container auflisten lassen:
docker ps
eb77b9f37140 … 0.0.0.0:9090->8080/tcp
Die Auflistung enthält üblicherweise Container ID, Image, Command, Created, Status, Ports und Names. Für uns interessant sind im
weiteren Verlauf vor allem die Ports. Wir erhalten im Beispiel die Route 0.0.0.0:9090, das hatten wir so aber nicht
angegeben.
Macht doch nichts - ist schließlich Standard, oder? Naja, das kommt darauf an wo wir uns befinden, denn 0.0.0.0 bedeutet, dass
der Port für alle Adressen offen ist. Um die Problematik besser zu verstenen, betrachten wir die Netzwerkschicht.
Netzwerk Schnittstellen
Die nachfolgende Abbildung veranschaulicht den Aufbau eines nicht docker-fähigen Host Systems. Das bedeutet, dass Docker in
einer Virtuellen Maschine (VM) auf dem Host betrieben wird. Zunächst betrachten wir unseren Container.
Das Netzwerk Interface eth0 sowie lo (loopback) sind offen für den Port 8080. Dabei bietet eth0 diese Schnittstelle nach außen
an.
Die nächste Abbildung zeigt die Kommunikation der docker-engine. Die docker-engine managed die Container und stellt das
Interface für die Kommunikation bereit - docker0. Der
Container ist über ein weiteres virtuelles Interface vethXY nach docker0 verbunden.
Somit wandern Anfragen an docker0, über vethXY schließlich an das eth0 Interface unseres Containers.
Damit unsere Anfrage an docker0 geleitet werden docker-proxy eingesetzt. Diese lauschen auf den gewünschten Ports und
leiten entsprechend weiter:
… /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 9090 -container-ip 172.17.0.2
-container-port 8080
Hier sehen wir nun auch wo die magische 0.0.0.0 herkommt.
Um nun das Problem nochmal zu verdeutlichen komplettiet die nachfolgende Abbildung nochmals die Struktur der Host Maschine:
Die IP 0.0.0.0 hängt am Interface der VM und ist nicht weiter nach außen freigegeben. Somit können wir von unserem Browser zwar
auf
die Anwendung unter dem Port zugreifen, ein Dritter von außen jedoch nicht. Der Port 9090 ist nicht auf unserer Host Maschine
direkt sondern in der VM freigegeben.
Jetzt verschieben wir unsere Test Anwendung auf einen Production Server aus der Cloud, einen auf Linux basiertern, dockerfähigen
Host:
Die VM ist weggefallen da unser Host docker direkt betreiben kann. Nun lauschen die docker-proxies direkt auf unserem Host
Interface und offenbaren Ports die wir vielleicht garnicht nach außen sichtbar haben wollen. Überprüfen können wir das mittels
nmap
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 14:40 CEST
Nmap scan report for my.server
Host is up (0.056s latency).
PORT STATE …
9090/tcp open …
Der Port 9090 ist also für die ganze Welt offen - bewusst oder unbewusst.
Das sollte unterbunden werden, da ansonsten eine potenzielle Sicherheitslücke besteht. Um dies zu vermeiden, eignet sich die
Verwendung des bereits erwähnten
lo
(loopback) Interface.
Port sichern
Um den Port nach außen nicht freizugeben, teilen dem Container mit dass dieser statt auf 0.0.0.0 nur auf das lo Interface
lauschen soll.
docker run -p 127.0.0.1:9090:8080
Mit dieser Einschränkung ist der Container innerhalb unseres Systems weiterhin unter dem Port 9090 erreichbar, nach außen hin
jedoch gesperrt. Überprüfen wir das nochmal:
ps -ef | grep docker-proxy
… /usr/bin/docker-proxy -proto tcp -host-ip 127.0.0.1 -host-port 9090 -container-ip 172.17.0.2 -container-port 8080
nmap my.server -p 9090
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-28 15:04 CEST
Nmap scan report for my.server
Host is up (0.061s latency).
PORT STATE …
9090/tcp closed …
Der Port 9090 ist also nach außen geschlossen und lediglich intern erreichbar.
Fazit
Der Einstieg in die docker Welt ist einfach und ermöglicht es auf schnell und einfache Weise Dienste zur Verfügung zu stellen.
Damit
das auch so einfach ist, geschieht viel unter der Haube, was jedoch zu Überraschungen führen kann.
Das Öffnen der Ports nach außen ist sicherlich nicht immer gewollt und somit ein Sicherheitsrisiko wenn man docker ohne zu
Überprüfen genauso
einsetzt wie es geläufige Tutorials nun mal so machen.