Willkommen zum ersten Teil meiner Docker-Serie. Ich versuche in diesem mehrteiligen Tutorial darzustellen, wie man Docker auf einem VServer verwenden kann. Wenn ihr so etwas vor habt, müsst ihr euch dessen bewusst sein, dass dieser Server 24/7 im Internet steht. Der Server muss also laufend aktualisiert und auch überwacht werden. Dies gilt für den Host selbst, aber natürlich auch für die Dockercontainer! Wo immer möglich, solltet ihr Zugriffe einschränken, auf IPtables (ufw) zurückgreifen und ein Monitoring und Update-tool installieren.
💡 WICHTIG: Das hier ist KEINE Docker-Rootless-Installation! D.h. die Prozesse innerhalb der Dockercontainer laufen mit dem User root. Selbst wenn man Container mit einem Standarduser ausrollt, heißt es nicht, dass somit alle anderen Prozesse (der Container) „rootless“ laufen.
Ich habe den Zugriff auf die Dienste, die vom Internet aus zugreifbar sind, auf meine 2 statischen öffentlichen IPs eingeschränkt. Sofern ihr aus Sicherheitsgründen Docker ohne Root verwenden wollt, gibt es hier eine ganz gute Anleitung (allerdings auf englischer Sprache). Ich werde mich dem Thema später zuwenden, jedoch nicht innerhalb dieser Artikelserie.
Zielsetzung
- Auf einem Server, der im Internet steht, oder aus dem Internet erreichbar ist, wird -basierend auf Ubuntu Server – Docker installiert.
- Es ist bereits ein Full-Qualified-Domain-Name (eine Domain) registriert, die auf den Dockerhost zeigt. In meinem Fall ist das portainer.it-networker.at
- Als Verwaltungstool kommt Portainer zum Einsatz.
- Zum Schutz von Host und Client wird UFW eingesetzt.
- Nachdem die gesamte Netzwerkkonnektivität von Docker via iptables gesteuert wird, wird UFW adapiert, damit die – mittels UFW- gesetzten Regeln greifen.
ACHTUNG: Eine nicht-adaptierte Konfiguration von UFW hat zur Folge, dass -sofern man Containerports „exposed“ = nach außen öffnet- jeder von überall aus darauf Zugriff hat! (Mehr dazu unten in der Anleitung) - Es wird NGINX-Proxy-Manager installiert, der die Verbindungen von außen zu den angebotenen Webseiten / Services „verwaltet“.
- Über den NGINX-Proxy-Manager werden via Letsencrypt SSL Zertifikate zur Verfügung gestellt.
- Fail2Ban (im Dockercontainer) sorgt für die zusätzliche Absicherung von Host und Container.
Die Installation von ufw
Ich gehe davon aus, dass man bereits via SSH auf dem Server (=Dockerhost) als root eingeloggt ist. In meinem Fall kommt Ubuntu „focal fossa“ – 20.4.5 LTS zum Einsatz. Ufw ist mit wenigen Befehlszeilen installiert, jedoch noch nicht aktiviert! Bitte aktiviert ufw nicht sofort nach der Installation, da dadurch eine gute Chance besteht, dass ihr euch aus dem System ausperrt! (via SSH)
apt-get update && apt-get upgrade -y
apt-get install ufw -y
Installation von Docker
Bezüglich Docker-Installation halte ich mich an: https://docs.docker.com/engine/install/ubuntu/
apt-get update
apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
Testen der Installation
docker run hello-world
Hier sollte dann eine entsprechende Ausgabe des „Hello-World“ Images erscheinen. Der Container bzw. das Image sollten später wieder gelöscht werden. Ich erledige das via Portainer. (Mehr dazu folgt).
Ausgabe von docker run hello-world
Unable to find image ‚hello-world:latest‘ locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:faa03e786c97f07ef34423fccceeec2398ec8a5759259f94d99078f264e9d7af
Status: Downloaded newer image for hello-world:latestHello from Docker!
This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the „hello-world“ image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/For more examples and ideas, visit:
https://docs.docker.com/get-started/
Adaptieren / Anpassen von ufw (after.rule)
Wie im Artikel auf https://github.com/chaifeng/ufw-docker beschrieben, muss /etc/ufw/after.rule um einige Zeilen ergänzt werden. Konkret ist am Ende der Datei Folgendes anzuhängen:
# BEGIN UFW AND DOCKER
*filter
:ufw-user-forward - [0:0]
:ufw-docker-logging-deny - [0:0]
:DOCKER-USER - [0:0]
-A DOCKER-USER -j ufw-user-forward
-A DOCKER-USER -j RETURN -s 10.0.0.0/8
-A DOCKER-USER -j RETURN -s 172.16.0.0/12
-A DOCKER-USER -j RETURN -s 192.168.0.0/16
-A DOCKER-USER -p udp -m udp --sport 53 --dport 1024:65535 -j RETURN
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 192.168.0.0/16
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 10.0.0.0/8
-A DOCKER-USER -j ufw-docker-logging-deny -p udp -m udp --dport 0:32767 -d 172.16.0.0/12
-A DOCKER-USER -j RETURN
-A ufw-docker-logging-deny -m limit --limit 3/min --limit-burst 10 -j LOG --log-prefix "[UFW DOCKER BLOCK] "
-A ufw-docker-logging-deny -j DROP
COMMIT
# END UFW AND DOCKER
Nach der Anpassung ist es am Besten den Server neu zu starten, da es – wie auch im Artikel auf Github erwähnt – dazu kommen kann, dass die Anpassung erst nach einem Neustart greift.
Ufw Regeln setzen
Nun sollte man so schnell wie möglich den Zugriff auf den Server (der ja auch im Internet stehen kann) einschränken.
Dies geschieht mit einigen wenigen ufw Kommandos. Ich nehme an, dass wir alle ausgehenden Verbindungen erlauben wollen und grundsätzlich einmal alle eingehenden Verbindungen „abdrehen“.
Bevor jedoch ufw aktiviert wird, erlauben wir SSH = Port 22 eingehend. Sofern ihr bei euch eine statische IP Adresse im Einsatz habt (bei eurem Internetzugang, von dem aus ihr auf euren Server zugreift) solltet ihr jedenfalls den SSH Zugang nur auf diese IP einschränken.
ufw default deny incoming
ufw default allow outgoing
ufw allow in from <EURE EXTERNE IP> to any port 22
Obige Port 22 Regel ist euer Zugang zum SSH-Server.
Status Quo bzgl. UFW
Aufgrund unserer Anpassung ist der Status der Firewall nun so, dass alle ausgehenden Verbindungen erlaubt sind. Eingehende Verbindungen sind jedoch ausschließlich auf Port 22 des Hosts erlaubt. Selbst wenn wir nun im Folgenden einen Container (Portainer) installieren, darf dieser Container NICHT (von außen) erreichbar sein. Ist das der Fall, habt ihr bei der UFW Adaptierung vermutlich irgendwo einen Fehler eingebaut.
Installation von Portainer
Auch hier halte ich mich an die offizielle Anleitung: https://docs.portainer.io/start/install/server/docker/linux
Anmerkung: Um nicht immer imt dem root-user zu arbeiten, sollte man sich einen Standarduser anlegen:
adduser <username>
Danach wird man nach dem Passwort gefragt, welches man für den User setzen will. Ebenso werden einige weitere optionale Details abgefragt.
Den Standarduser der Dockergruppe hinzufügen
Um nun auch mit dem gerade angelegten Benutzer „Docker-Aktionen“ durchführen zu können, wir der User der Gruppe docker hinzugefügt:
usermod -aG docker <username>
docker ist hierbei die Gruppe Docker!
Folgende Befehle (ausgenommen ufw) können nun mit dem Standardbenutzer durchgeführt werden. Allerdings muss man sich dafür auch mit diesem User per SSH auf dem Server anmelden.
Portainer Volume erstellen & Portainer (CE) starten
docker volume create portainer_data
docker run -d -p 8000:8000 -p 9443:9443 --name portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest
Ausgabe des obigen Befehls
Unable to find image ‚portainer/portainer-ce:latest‘ locally
latest: Pulling from portainer/portainer-ce
772227786281: Pull complete
96fd13befc87: Pull complete
0bad1d247b5b: Pull complete
b5d1b01b1d39: Pull complete
Digest: sha256:f7607310051ee21f58f99d7b7f7878a6a49d4850422d88a31f8c61c248bbc3a4
Status: Downloaded newer image for portainer/portainer-ce:latest
59838779c0bd225e6d81966a8bae6484abcea80fb89642221fda6d35b325f142
Prüfung ob der Container läuft
docker stats
Wie man sieht, läuft der Container „Portainer“.
WICHTIG
Es sind einige Ports nach außen freigegeben. Sofern die Adaptierung von UFW korrekt verlaufen ist, darf hier jetzt noch KEIN Zugriff möglich sein. Bitte unbedingt testen!!!
Ihr könnt z.B. auch einen Online-Security-Scanner verwenden, um eure Domain (auf der aktuell Docker & Portainer läuft) auf offene Ports zu scannen. Davon ausgehend, dass ihr nur eure öffentliche IP für den Zugriff auf euren Dockerhost (Portainer) freigegeben habt, müsste ein Scanergebnis (zb. von https://pentest-tools.com) so aussehen (0 open ports!):
Wir testen den Aufruf via meiner Domain, die ich auf den Docker-Host „zeigen“ lasse. (9443 = der Portaineradminport)
https://container.it-networker.at:9443
Nachdem die adaptierte UFW – Firewall funktioniert, erhalte ich korrekt:
Wir müssen nun Port 9443 öffnen. Dies erreichen wir über folgenden Befehl:
ufw route allow from <ip die Zugriff haben soll> to any port 9443
Ich greife in dem Fall direkt auf Portainer zu, da ich den Zugriff auf meine statische öffentliche IP einschränke! Wenn ihr keine statische öffentliche IP habt, dann müsst ihr hier aus Sicherheitsgründen jedenfalls über den NGINX Reverse Proxy mit Authentifizierung für Portainer „fahren“. (Später dazu mehr!)
Wichtig hierbei ist, dass wir UNBEDINGT ufw route allow verwenden müssen, wenn wir via UFW Zugriff auf einen Container gestatten wollen. Ein bloßes ufw allow from …. reicht hier nicht und würde sich nur direkt auf den Host beziehen, nicht aber auf den Container!
Beim erneuten Aufruf von https://container.it-networker.at:9443 erscheint nun eine Sicherheitswarnung (weil Portainer ein selfsigned-SSL-Zertifikat verwendet). Das ist ok. Folgend müssen wir nur noch ein neues Kennwort setzen und können Portainer nutzen.
Bitte testet unbedingt auch, ob ihr von einem anderen Gerät aus (mit einer anderen öffentlichen IP) auf den Portainer – Port 9443 kommt. Wenn ihr den Zugriff auf eure öffentliche statische IP eingeschränkt habt und ihr auch mit einer anderen öffentlichen IP auf eure Portainerinstallation zugreifen könnt, dann stimmt etwas bei eurer UFW-Konfigruation nicht!