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 Hostmaschine
direkt sondern in der VM freigegeben.
Jetzt verschieben wir unsere Testanwendung 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 gar nicht 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.