Docker und ufw

Mi, 20. Oktober 2021, Lioh Möller

Unter Ubuntu und vielen anderen Linux-Distributionen kommt oftmals ufw (Uncomplicated Firewall) zum Einsatz, um das System abzusichern. Im Zusammenspiel mit Docker werden aber schnell einige Schwächen deutlich. Standardmässig erstellt Docker eigene Firewall Regeln für den Zugriff auf die Container. Diese werden allerdings vor den Regeln von ufw eingefügt, wodurch letztere nichtig werden.

Schränkt man beispielsweise mit ufw die Zugangsmöglichkeit auf einem in Docker laufenden Webserver auf ein bestimmtes Netzwerk ein, wäre dieser dennoch von überall her möglich.

Es folgt eine übliche Regel, um den Zugriff auf Port 443 nur aus dem lokalen Netz 192.168.0.0/24 zuzulassen:

sudo ufw allow from 192.168.0.0/24 to any port 443

Würde man nun versuchen vom Netz 192.168.100.0/24 auf Port 443 zuzugreifen, wäre dies weiterhin möglich. Als Administrator wägt man sich schnell in Sicherheit, denn das Regelwerk ist grundsätzlich richtig, Docker jedoch hebelt es wieder aus.

Einen erprobten Ansatz zur Lösung dieses Problems bietet der Entwickler Chai Feng. Im Gegensatz zum oftmals empfohlenen Vorgang, die iptables Funktionalität in Docker vollständig abzuschalten, wählt er einen nachhaltigeren Ansatz.

Zunächst muss sichergestellt werden, dass in der Datei /etc/docker/daemon.json nicht die Option "iptables": false gesetzt ist. In der Datei /etc/default/ufw muss ausserdem die folgende Option aktiviert sein, was standardmässig der Fall ist:

DEFAULT_FORWARD_POLICY="DROP"

Alle vorgängig gemachten und für Docker relevanten Einstellungen in der Datei /etc/ufw/after.rules müssen entfernt werden.

Daraufhin wird am Ende der Datei /etc/ufw/after.rules folgender Abschnitt eingefügt:

# 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

Zur Aktivierung der Änderungen muss ufw mithilfe von sudo ufw reload neu gestartet werden.

Um nun den Zugriff auf einem im Container auf Port 443 laufenden Webserver aus dem Netz 192.168.0.0/24 zu erlauben, kann folgende Regel erstellt werden:

ufw route allow proto tcp from 192.168.0.0/24 to any port 443

Wichtig ist dabei, dass immer der Container-Port angegeben wird. In vielen Fällen wird dieser auf einen abweichenden Host-Port publiziert. Die Docker Option -p 8080:80 beispielsweise veröffentlicht den Container-Port 80 auf den Host-Port 8080. In diesem Falle müsste in der obigen Regel der Container-Port 80 angegeben werden.

In einer docker-compose.yml werden Ports im folgenden Format angegeben:

   ports:
      - "8080:80"

Wobei hier 80 der Port im Container wäre und 8080 der auf dem Host publizierte Port.

Zu beachten ist ausserdem, dass von nun an zum Zugriff auf Docker Container mittels Forward-Regeln in der Form ufw route und keine Allow-Regeln wie eingangs aufgeführt erstellt werden müssen.

Weitere Informationen finden sich auf im ufw-docker Git-Repository