Powrot do wpisow

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

  1. Mechanizm: dlaczego publikacja portów może ominąć firewall
  2. Runtime: jak reguły pojawiają się i znikają razem z kontenerami
  3. 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:

  • -p nie modyfikuje hostowego iptables
  • 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:

CechaRootful DockerRootless Docker
Dostęp do iptablesTakNie
Modyfikacja firewalla hostaTakNie
Zachowanie względem UFWProblematycznePrzewidywalne
Wydajność sieciNatywnaZwykle niższa
Uprawnienia procesuRootZwykł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.