Powrot do wpisow

Wpis techniczny

Docker, UFW i publikacja portów: jak odzyskać kontrolę nad ekspozycją usług

Praktyczny domykający wpis serii: jak kontrolować publikację portów w Dockerze, gdzie kończy się UFW i jak nie wystawiać usług przypadkiem.

Jeśli pracujesz z Dockerem na serwerze, temat publikacji portów warto rozłożyć na trzy osobne warstwy.

Pierwsza to mechanizm, czyli to, jak Docker modyfikuje iptables i dlaczego potrafi ominąć systemowy firewall. Druga to runtime, a więc jak reguły pojawiają się i znikają razem z kontenerami. Trzecia to praktyka: jak to wszystko utrzymać pod kontrolą w realnym środowisku.

Ten wpis dotyczy właśnie trzeciego elementu.

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 z tego wynika w praktyce

Jeżeli wiesz już, że rootful Docker dopisuje własne reguły do iptables, a publikacja portów nie jest tylko niewinnym mapowaniem, to naturalne pytanie brzmi: jak z tym żyć na co dzień.

Problem nie polega na tym, że Docker “robi coś źle”. Problem polega raczej na tym, że bardzo łatwo pomylić wygodę z kontrolą. Wystarczy kilka kontenerów uruchamianych z -p, a polityka dostępu zaczyna żyć poza jednym czytelnym miejscem.

Dlaczego sam UFW nie wystarczy

Na Ubuntu łatwo przyjąć prosty model:

ufw enable

i założyć, że od tej chwili cały ruch przychodzący jest pod kontrolą jednej warstwy.

Przy Dockerze to założenie szybko przestaje być bezpieczne. UFW nadal działa, ale nie daje pełnego obrazu tego, co zostało wystawione przez kontenery. Część decyzji o ruchu zapada w regułach, które Docker dodaje samodzielnie.

W praktyce oznacza to, że:

  • ufw status nie pokazuje całej prawdy
  • docker run -p ... może od razu wystawić usługę na zewnątrz
  • audyt trzeba prowadzić jednocześnie na poziomie hosta i runtime

Najbezpieczniejszy punkt wyjścia

Najprostsza i najczęściej sensowna zasada brzmi: nie publikuj portów globalnie, jeśli nie ma takiej potrzeby.

Zamiast tego wystaw usługę lokalnie:

docker run -d -p 127.0.0.1:8080:80 nginx

W takim układzie kontener jest osiągalny z hosta, ale nie otwierasz go od razu na wszystkie interfejsy.

Dopiero potem decydujesz, czy ruch ma przejść przez UFW, reverse proxy albo inną kontrolowaną warstwę wejściową.

Dwa modele pracy

W praktyce spotkasz zwykle dwa podejścia.

1. Docker sam zarządza ekspozycją usług

W tym modelu po prostu używasz -p, a Docker zajmuje się resztą.

To rozwiązanie jest szybkie i wygodne, ale ma swoje koszty:

  • łatwo wystawić usługę szerzej, niż planowałeś
  • trudniej audytować stan dostępu
  • polityka bezpieczeństwa zaczyna zależeć od pojedynczych poleceń uruchomieniowych

Taki model bywa akceptowalny w prostych środowiskach testowych, ale na serwerze współdzielonym albo produkcyjnym szybko robi się nieczytelny.

2. Docker uruchamia usługę, a dostęp kontrolujesz osobno

To podejście jest bardziej przewidywalne. Kontener działa lokalnie albo w sieci wewnętrznej, a decyzja o ekspozycji zapada dopiero na poziomie UFW, reverse proxy albo centralnego ingressu.

Korzyści są dość konkretne:

  • łatwiej odtworzyć politykę dostępu
  • łatwiej przeprowadzić audyt
  • trudniej o przypadkowe wystawienie portu

Cena jest prosta: trzeba świadomie zaplanować warstwę wejściową zamiast polegać wyłącznie na docker run.

Co warto przyjąć jako standard

Jeżeli Docker działa u Ciebie na serwerze dłużej niż do jednego testu, dobrze mieć kilka prostych zasad.

Publikuj porty tylko tam, gdzie to uzasadnione

Nie każdy kontener powinien mieć własny publiczny port. Zwykle wystarczy, że publicznie widoczna jest tylko warstwa brzegowa, na przykład Nginx, Caddy albo Traefik.

Sprawdzaj runtime, nie tylko konfigurację

Żeby zobaczyć, co naprawdę jest wystawione, zacznij od bieżącego stanu kontenerów:

docker ps --format "table {{.Names}}\t{{.Ports}}"

To lepszy punkt startowy niż samo założenie, że skoro UFW czegoś nie pokazuje, to temat nie istnieje.

Używaj DOCKER-USER, jeśli musisz filtrować ruch Dockera

Jeżeli zostajesz przy rootful Dockerze i chcesz odzyskać część kontroli nad ruchem, sensownym miejscem na własne reguły jest łańcuch DOCKER-USER.

iptables -L DOCKER-USER
iptables -I DOCKER-USER -p tcp --dport 8080 -j DROP

To nie zastępuje dobrej architektury wejścia, ale bywa przydatne jako dodatkowy bezpiecznik.

A co z rootless Dockerem

Rootless Docker upraszcza część problemu, bo nie modyfikuje hostowego iptables w ten sam sposób co rootful. Nie oznacza to jednak, że możesz przestać myśleć o ekspozycji usług.

Nadal trzeba wiedzieć:

  • które porty są publikowane
  • komu i po co są potrzebne
  • czy powinny być publiczne, lokalne czy dostępne wyłącznie przez proxy

Rootless poprawia przewidywalność. Nie zwalnia z porządku operacyjnego.

Najczęstsze błędne założenia

”Mam UFW, więc wszystko jest zamknięte”

Nie zawsze. Jeśli kontener został opublikowany przez -p, trzeba sprawdzić również reguły dodane przez Dockera i aktualny runtime.

expose działa tak samo jak -p

Nie działa.

expose:
  - 80

expose informuje o porcie w sieci Dockera. Nie wystawia go automatycznie na hosta.

”Skoro nic nie zmieniałem w firewallu, to nic nie jest wystawione”

To też bywa mylące. W Dockerze stan ekspozycji usług może wynikać z samego uruchomienia kontenera.

Wniosek

Najpraktyczniejszy model jest dość prosty: Docker odpowiada za uruchamianie usług, ale decyzja o tym, co ma być osiągalne z zewnątrz, powinna być możliwie scentralizowana i czytelna.

Jeżeli nie musisz publikować portu globalnie, nie rób tego. Jeżeli musisz, traktuj -p jak świadomą decyzję bezpieczeństwa, a nie jak techniczny detal.

Żeby dobrze domknąć temat, warto czytać te trzy wpisy razem: najpierw mechanizm działania reguł Dockera, potem ich cykl życia w runtime, a na końcu praktykę kontroli ekspozycji usług, czyli właśnie ten artykuł.