Przejdź do treści głównej

Opublikowano

Opublikowane porty Dockera omijają UFW

Podczas incydentu DDoS okazało się, że opublikowany port kontenera odpowiadał z internetu, chociaż UFW na hoście był włączony. Docker dopisał dla tego portu własne reguły iptables i żadna z moich reguł UFW tego ruchu nie obejmowała.

  • Docker
  • UFW
  • iptables
  • DOCKER-USER
  • firewall
  • Linux

Dlaczego UFW nie filtruje opublikowanego portu

Reguły dodawane przez ufw allow i ufw deny dotyczą ruchu adresowanego do samego hosta. Opublikowany port działa inaczej. Przy -p 8080:80 Docker dodaje regułę DNAT, która podmienia adres docelowy na adres kontenera, więc pakiet jest przekazywany do kontenera, a nie dostarczany do hosta.

Ruch przekazywany przechodzi przez łańcuchy Dockera, które przepuszczają go, zanim dojdzie do reguł UFW. ufw status nadal pokazuje domyślne blokowanie ruchu przychodzącego, a kontener i tak odpowiada.

W czasie incydentu aplikacja działała w kontenerze z opublikowanym portem i bez reverse proxy przed nim. Kto znał IP originu, łączył się z aplikacją bezpośrednio, z pominięciem Cloudflare i firewalla na hoście.

Jak sprawdzić serwer z zewnątrz

Test uruchomiony na samym serwerze niczego tu nie dowodzi, bo połączenia lokalne idą przez firewall inną ścieżką. Sprawdzenie musi przyjść z innej sieci.

Na hoście docker ps pokazuje, które porty są przypięte do 0.0.0.0 albo [::], czyli do wszystkich interfejsów. Każdy z nich, który UFW ma blokować, sprawdź z zewnątrz.

  • Z innej sieci: nmap -Pn -p 8080 <IP originu> albo curl -v http://<IP originu>:8080/.
  • Na hoście: docker ps --format "{{.Names}} {{.Ports}}", żeby zobaczyć opublikowane porty.
  • Na hoście: iptables -L DOCKER-USER -n -v, żeby sprawdzić, czy coś je filtruje.

Trzy sposoby na zamknięcie portu

Wybór zależy od tego, czy cokolwiek spoza hosta w ogóle potrzebuje tego portu.

Ustawienie "iptables": false w /etc/docker/daemon.json powstrzymuje Dockera przed dopisywaniem reguł, ale wtedy kontenery tracą NAT potrzebny do ruchu wychodzącego, dopóki ktoś nie napisze tych reguł ręcznie.

Filtrowania w DOCKER-USER nie trzeba konfigurować ręcznie. Sam użyłem ufw-docker, skryptu open source, który dopisuje reguły do /etc/ufw/after.rules i sprawia, że DOCKER-USER sprawdza reguły dodane przez ufw route. Docker dalej sam zarządza iptables, nowe połączenia z publicznych adresów do kontenerów są blokowane, a port otwiera się poleceniem ufw route allow albo ufw-docker allow <kontener> <port>. Reguła dotyczy portu kontenera, a nie hosta, więc przy -p 8080:80 otwierasz port 80. Domyślnie skrypt przepuszcza też bez filtrowania cały ruch z prywatnych zakresów 10.0.0.0/8, 172.16.0.0/12 i 192.168.0.0/16, co ma znaczenie, gdy serwer stoi w prywatnej sieci dostawcy; zawęża to opcja --docker-subnets.

  • Przypnij port do localhosta, np. "127.0.0.1:8080:80" w Docker Compose, i postaw przed nim reverse proxy na hoście. Na publicznym interfejsie nasłuchuje wtedy tylko proxy. To najprostsza poprawka, gdy publiczne ma być tylko proxy; kosztem jest jedna usługa więcej do skonfigurowania.
  • Filtruj w łańcuchu DOCKER-USER. Docker kieruje przez niego ruch przekazywany przed własnymi regułami, więc DROP w tym miejscu działa, a reguły ACCEPT nad nim przepuszczają zaufane źródła. Port docelowy jest już wtedy podmieniony na port kontenera, więc trzeba dopasować ten port albo użyć -m conntrack --ctorigdstport dla opublikowanego, a reguły zapisać na stałe, np. w /etc/ufw/after.rules.
  • Za Cloudflare przyjmuj ruch na portach 80 i 443 tylko z opublikowanych zakresów IP Cloudflare. Przy Dockerze ta lista też musi siedzieć w DOCKER-USER albo na proxy przypiętym do hosta, z powodu opisanego wyżej, a zakresy trzeba aktualizować, gdy Cloudflare je zmienia.

Co zrobiłem w czasie incydentu

Zmieniłem i odizolowałem adres IP originu, ograniczyłem drogi, którymi można było dotrzeć do serwera, i dopiero potem stroiłem WAF i rate limiting w Cloudflare. Resztę incydentu opisują case study i notatka o Cloudflare i odsłoniętym originie.

Zobacz też: Cloudflare nie chroni originu dostępnego bezpośrednio, CI/CD i wdrożenia na Dockerze.

Jeśli chcesz porozmawiać o podobnej konfiguracji albo problemie, napisz przez stronę kontaktową.

Powiązany projekt

Powiązane case study: