Dockers nft-Inkompatibilität wird zunehmend zum Ärgernis
Ca. seit 2020 kommt nftables (Kommando nft) per Default als Firewall-Backend unter Linux zum Einsatz. Manche Distributionen machten den Schritt noch früher, andere folgten ein, zwei Jahre später. Aber mittlerweile verwenden praktisch alle Linux-Distributionen nftables.
Alte Firewall-Scripts mit iptables funktionieren dank einer Kompatibilitätsschicht zum Glück größtenteils weiterhin. Viele wichtige Firewall-Tools und -Anwendungen (von firewalld über fail2ban bis hin zu den libvirt-Bibliotheken) brauchen diese Komaptibilitätsschicht aber nicht mehr, sondern wurden auf nftables umgestellt.
Welches Programm ist säumig? Docker! Und das wird zunehmend zum Problem.
Docker versus libvirt/virt-manager
Auf die bisher massivsten Schwierigkeiten bin ich unter Fedora >=42 und openSUSE >= 16 gestoßen: Wird zuerst Docker installiert und ausgeführt, funktioniert in virtuellen Maschinen, die mit libvirt/virt-manager gestartet werden, das NAT-Networking nicht mehr. Und es bedarf wirklich einiger Mühe, den Zusammenhang mit Docker zu erkennen. Die vorgebliche »Lösung« besteht darin, die libvirt-Firewall-Funktionen von nftables zurück auf iptables zu stellen. Eine echte Lösung wäre es, wenn Docker endlich nftables unterstützen würde.
# in der Datei /etc/libvirt/network.conf
firewall_backend = "iptables"
Danach starten Sie den libvirt-Dämon dann neu:
sudo systemctl restart libvirtd
Weitere Infos gibt es hier und hier. Die openSUSE-Release-Notes weisen ebenfalls auf das Problem hin.
Sicherheitsprobleme durch offene Ports
Weil Docker iptables verwendet, ist es mit nftables oder firewalld nicht möglich, Container mit offenen Ports nach außen hin zu blockieren. Wenn Sie also docker run -p 8080:80 machen oder in compose.yaml eine entsprechende Ports-Zeile einbauen, ist der Port 8080 nicht nur auf dem lokalen Rechner sichtbar, sondern für die ganze Welt! nftables- oder firewalld-Regeln können dagegen nichts tun!
Deswegen ist es wichtig, dass Docker-Container möglichst in internen Netzwerken miteinander kommunizieren bzw. dass offene Ports unbedingt auf localhost limitiert werden:
# Port 8080 ist nur für localhost zugänglich.
docker run -p localhost:8080:80 ...
Ihre Optionen in compose.yaml sehen so aus:
```bash
# compose.yaml
# Ziel: myservice soll mit anderem Container
# in compose.yaml über Port 8888 kommunizieren
services:
myservice:
image: xxx
ports:
# unsicher!
# - "8888:8888"
# besser (Port ist für localhost sichtbar)
# - "127.0.0.1:8888:8888"
# noch besser (Port ist nur Docker-intern offen)
- "8888"
otherservice:
...
Die sicherste Lösung besteht darin, die Container ausschließlich über ein Docker-internes Netzwerk miteinander zu verbinden (siehe backend_network im folgenden schablonenhaften Beispiel). Die Verbindung nach außen für die Ports 80 und 443 erfolgt über ein zweites Netzwerk (frontend_network). Die Angabe des drivers ist optional und verdeutlicht hier nur den Default-Netzwerktyp.
# compose.yaml
# am besten: die beiden Services myservice und
# nginx kommunizieren über das interne Netzwerk
# miteinander
services:
myservice:
build: .
ports:
- "8888:8888"
networks:
- backend_network
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- /etc/mycerts/fullchain.pem:/etc/nginx/ssl/nginx.crt
- /etc/mycerts/privkey.pem:/etc/nginx/ssl/nginx.key
depends_on:
- myservice
networks:
- frontend_network
- backend_network
networks:
# Verbindung zum Host über eine Bridge
frontend_network:
driver: bridge
# Docker-interne Kommunikation zwischen den Containern
backend_network:
driver: bridge
internal: true
Restriktive Empfehlungen
Docker hat es sich zuletzt sehr einfach gemacht. Auf https://docs.docker.com/engine/install/ubuntu/#firewall-limitations steht:
If you use ufw or firewalld to manage firewall settings, be aware that when you expose container ports using Docker, these ports bypass your firewall rules. For more information, refer to Docker and ufw.
Docker is only compatible with iptables-nft and iptables-legacy. Firewall rules created with nft are not supported on a system with Docker installed. Make sure that any firewall rulesets you use are created with iptables or ip6tables, and that you add them to the DOCKER-USER chain, see Packet filtering and firewalls.
Und https://docs.docker.com/engine/security/#docker-daemon-attack-surface gibt diese Zusammenfassung:
Finally, if you run Docker on a server, it is recommended to run exclusively Docker on the server, and move all other services within containers controlled by Docker. Of course, it is fine to keep your favorite admin tools (probably at least an SSH server), as well as existing monitoring/supervision processes, such as NRPE and collectd.
Salopp formuliert: Verwenden Sie für das Docker-Deployment ausschließlich für diesen Zweck dezidierte Server und/oder verwenden Sie bei Bedarf veraltete iptable-Firewalls. Vor fünf Jahren war dieser Standpunkt noch verständlich, aber heute geht das einfach gar nicht mehr. Die Praxis sieht ganz oft so aus, dass auf einem Server diverse »normale« Dienste laufen und zusätzlich ein, zwei Docker-Container Zusatzfunktionen zur Verfügung stellen. Sicherheitstechnisch wird dieser alltägliche Wunsch zum Alptraum.
Land in Sicht?
Bei Docker weiß man natürlich auch, dass iptables keine Zukunft hat. Laut diesem Issue sind 10 von 11 Punkte für die Umstellung von iptables auf nftables erledigt. Aber auch dann ist unklar, wie es weiter gehen soll: Natürlich ist das ein massiver Eingriff in grundlegende Docker-Funktionen. Die sollten vor einem Release ordentlich getestet werden. Einen (offiziellen) Zeitplan für den Umstieg auf nftables habe ich vergeblich gesucht.
Docker ist als Plattform-überschreitende Containerlösung für Software-Entwickler fast konkurrenzlos. Aber sobald man den Sichtwinkel auf Linux reduziert und sich womöglich auf Red-Hat-ähnliche Distributionen fokussiert, sieht die Lage anders aus: Podman ist vielleicht nicht hundertprozentig kompatibel, aber es ist mittlerweile ein sehr ausgereiftes Container-System, das mit Docker in vielerlei Hinsicht mithalten kann. Installationsprobleme entfallen, weil Podman per Default installiert ist. Firewall-Probleme entfallen auch. Und der root-less-Ansatz von Podman ist sicherheitstechnis sowieso ein großer Vorteil (auch wenn er oft zu Netzwerkeinschränkungen und Kompatibilitätsproblemen führt, vor allem bei compose-Setups).
Für mich persönlich war Docker immer die Referenz und Podman die nicht ganz perfekte Alternative. Aber die anhaltenden Firewall-Probleme lassen mich an diesem Standpunkt zweifeln. Die Firewall-Inkompatibilität ist definitiv ein gewichtiger Grund, der gegen den Einsatz der Docker Engine auf Server-Installationen spricht. Docker wäre gut beraten, iptables ENDLICH hinter sich zu lassen!
Quellen / Links
- https://github.com/moby/moby/issues/49634
- https://github.com/docker/for-linux/issues/1472
- https://fedoraproject.org/wiki/Changes/LibvirtVirtualNetworkNFTables\
- https://doc.opensuse.org/release-notes/x86_64/openSUSE/Leap/16.0/html/~|~release-notes-leap-160\
- https://docs.docker.com/engine/install/ubuntu/#firewall-limitations
- https://docs.docker.com/engine/network/packet-filtering-firewalls\
- https://docs.docker.com/engine/security/#docker-daemon-attack-surface