Wpis techniczny
Iluzja bezpieczeństwa: dlaczego porty Dockera omijają Twój systemowy firewall
Rootful Docker modyfikuje iptables i potrafi ominąć UFW. Zobacz, skąd bierze się ten efekt i dlaczego rootless działa inaczej.
Publikacja portu w Dockerze wygląda niewinnie. W praktyce jedno -p potrafi zmienić zachowanie hosta bardziej, niż sugeruje składnia polecenia.
To ważne zwłaszcza wtedy, gdy na serwerze działa już firewall, na przykład UFW, i zakładasz, że to on kontroluje cały ruch przychodzący.
Seria: Docker i firewall
- Mechanizm: dlaczego publikacja portów może ominąć firewall
- Runtime: jak reguły pojawiają się i znikają razem z kontenerami
- Praktyka: jak odzyskać kontrolę nad ekspozycją usług
Co naprawdę robi -p
Polecenie:
docker run -d -p 8080:80 nginx
nie sprowadza się do prostego “przekierowania portu”.
W standardowym trybie rootful Dockera demon działa z uprawnieniami roota i sam modyfikuje iptables. Dodaje reguły NAT, ustawia forwarding i buduje własne łańcuchy, takie jak DOCKER oraz DOCKER-USER.
Efekt jest prosty: port 8080 zaczyna być osiągalny z zewnątrz zgodnie z regułami dodanymi przez Dockera, a niekoniecznie zgodnie z tym, jak wyobrażasz sobie politykę UFW.
Dlaczego UFW nie zawsze to zatrzyma
UFW jest nakładką na iptables, ale nie zarządza całą tablicą w pełni arbitralnie. Operuje głównie na własnych łańcuchach i własnym modelu reguł.
Docker idzie inną drogą. Wstrzykuje własne reguły bezpośrednio do iptables, w tym do tras odpowiedzialnych za NAT i forwarding. W praktyce ruch do opublikowanego portu często nie przechodzi przez ścieżkę, którą administrator kojarzy z klasycznym filtrowaniem w INPUT.
To właśnie tutaj rodzi się iluzja bezpieczeństwa: port wydaje się zablokowany, bo UFW ma regułę deny, ale kontener nadal odpowiada, ponieważ Docker przygotował dla niego osobną ścieżkę.
Przykład:
ufw deny 8080
Taka reguła może nie dać oczekiwanego efektu, jeśli 8080 został wcześniej opublikowany przez Dockera w trybie rootful.
Jak to wygląda od strony iptables
Po uruchomieniu kontenera z publikacją portu zwykle zobaczysz dodatkowe reguły między innymi w tabeli nat:
iptables -t nat -L -n
Typowy efekt to reguła w stylu:
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80
Do tego dochodzą reguły w FORWARD:
iptables -L FORWARD -n
Na przykład:
ACCEPT all -- 0.0.0.0/0 172.17.0.2
To dobry moment, żeby złapać właściwy model mentalny:
- ruch do kontenera nie kończy się na klasycznym
INPUT - Docker korzysta z NAT i forwarding
- część decyzji zapada poza tym, co zwykle kojarzy się z prostą konfiguracją UFW
Rootful Docker: wygoda kosztem przewidywalności
Domyślna instalacja Dockera działa w trybie rootful. To oznacza, że:
- demon działa jako root
- ma prawo zarządzać
iptables - może samodzielnie otwierać ścieżki ruchu do kontenerów
Z perspektywy wygody to bardzo praktyczne. Z perspektywy bezpieczeństwa oznacza jednak, że Docker staje się aktywnym elementem polityki sieciowej hosta, a nie tylko narzędziem do uruchamiania kontenerów.
Rootless Docker działa inaczej
W trybie rootless sytuacja wygląda inaczej, bo Docker działa jako zwykły użytkownik i nie ma dostępu do systemowego iptables.
Zamiast bezpośrednio ingerować w firewall hosta, korzysta z mechanizmów user-space, takich jak slirp4netns czy rootlesskit. Publikacja portów nadal działa, ale nie przez bezpośrednie dopisywanie reguł do systemowego firewalla.
W praktyce oznacza to:
-pnie modyfikuje hostowegoiptables- UFW zachowuje się bardziej przewidywalnie
- izolacja uprawnień jest lepsza
- sieć bywa mniej wydajna niż w trybie rootful
Rootful vs rootless w praktyce
Najkrócej można to podsumować tak:
| Cecha | Rootful Docker | Rootless Docker |
|---|---|---|
Dostęp do iptables | Tak | Nie |
| Modyfikacja firewalla hosta | Tak | Nie |
| Zachowanie względem UFW | Problematyczne | Przewidywalne |
| Wydajność sieci | Natywna | Zwykle niższa |
| Uprawnienia procesu | Root | Zwykły użytkownik |
Jak ograniczyć ryzyko w rootful Dockerze
Jeśli zostajesz przy rootful Dockerze, warto przyjąć kilka prostych zasad.
1. Filtruj ruch w DOCKER-USER
Docker respektuje łańcuch DOCKER-USER, więc to jedno z lepszych miejsc na własne reguły:
iptables -L DOCKER-USER
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP
To podejście pozwala odzyskać część kontroli bez wyłączania mechanizmów sieciowych Dockera.
2. Nie publikuj portu globalnie, jeśli nie musisz
Jeżeli usługa ma być dostępna tylko lokalnie albo przez reverse proxy, binduj ją do localhost:
docker run -d -p 127.0.0.1:8080:80 nginx
Wtedy port nie będzie wystawiony na wszystkie interfejsy hosta.
3. Wystawiaj ruch przez reverse proxy
Zamiast publikować wiele portów bezpośrednio z kontenerów, lepiej często zakończyć ekspozycję na jednym punkcie wejścia, na przykład przez Nginx, Caddy albo Traefik.
To upraszcza kontrolę dostępu, TLS i audyt otwartych usług.
4. Wyłączanie iptables tylko świadomie
Docker pozwala wyłączyć automatyczne zarządzanie iptables:
{
"iptables": false
}
To jednak nie jest “bezpieczny przełącznik”, tylko tryb dla osób, które chcą samodzielnie utrzymywać NAT, routing i pełną logikę dostępu. Jeśli nie masz na to gotowej konfiguracji, łatwo zepsuć komunikację.
Wniosek
Flaga -p nie jest niewinnym dodatkiem do polecenia docker run. W rootful Dockerze oznacza realną ingerencję w sieć hosta i w jego firewall.
Jeżeli używasz UFW i zakładasz, że to on w pełni kontroluje ekspozycję usług, możesz dojść do błędnych wniosków. Docker potrafi przygotować własną ścieżkę dla ruchu i właśnie dlatego kontener bywa osiągalny mimo pozornie restrykcyjnych reguł.
Rootless Docker ogranicza ten problem, bo nie ma uprawnień do manipulowania iptables, ale robi to kosztem części wydajności i kompatybilności.
Najważniejsza praktyczna lekcja jest prosta: jeśli publikujesz porty z Dockera, traktuj to jak decyzję o wystawieniu usługi na hoście, a nie jak drobne techniczne przekierowanie.
Jeżeli chcesz zobaczyć, jak ten mechanizm wygląda w praktyce podczas startu i zatrzymywania kontenerów, zajrzyj też do wpisu o dynamicznym cyklu życia reguł firewalla w Dockerze.