UFW jest fajne. Wygodne, czytelne, w zupełności wystarcza większości ludzi.

Czasem jednak chcesz mieć pełną kontrolę nad tym, co dokładnie dzieje się z każdym pakietem. Albo lądujesz na serwerze, na którym UFW po prostu nie ma i nie będzie. Wtedy zostaje stare, dobre iptables, z którym warto się zaprzyjaźnić, nawet jeśli jego składnia na pierwszy rzut oka przypomina zaklęcie.

Zanim zaczniemy, jedno zastrzeżenie. Jeśli masz już włączony UFW, nie dokładaj do niego ręcznych reguł iptables. Wybierz jedno narzędzie i się go trzymaj. UFW trzyma w iptables własne łańcuchy, a wrzucanie między nie swoich reguł kończy się tym, że jedne wchodzą drugim w paradę.

Na Mikrusie#

Na obrazie Ubuntu, którego używamy na Mikrusie, iptables jest od razu. Niczego nie doinstalowujesz.

Jeśli zamiast ręcznego dłubania w regułach wolisz szybki, gotowy firewall, w NOOBS-ie znajdziesz skrypt oparty na UFW (który pod spodem i tak ustawia iptables za Ciebie). Ten wpis jest dla tych, którzy chcą to robić własnoręcznie.

Na dowolnym serwerze (Ubuntu/Debian)#

Wszystko, co poniżej, działa jednakowo na Mikrusie i na każdym innym VPS-ie z Ubuntu czy Debianem.

Na rozgrzewkę podejrzyjmy aktualne reguły dla ruchu przychodzącego, od razu z numerami:

sudo iptables -L INPUT -n --line-numbers

Zanim zaczniesz cokolwiek blokować, zapamiętaj jedną rzecz o IPv6, bo wiele osób o niej zapomina. Każdy Mikrus ma prawdziwy, publiczny adres IPv6, bez żadnego NAT-u. Po IPv4 wystawione są tylko przekierowane porty, a po IPv6 cały kontener stoi na widoku. Reguła w iptables obejmuje wyłącznie IPv4, więc po IPv6 nie chroni Cię w najmniejszym stopniu. Do tego służy bliźniacza reguła w ip6tables. Dlatego w każdym przykładzie niżej znajdziesz obie wersje.

Blokowanie portu#

Blokujemy ruch przychodzący na port 8080. Może tam siedzieć na przykład panel admina aplikacji, który nie ma czego szukać w publicznym internecie:

sudo iptables -A INPUT -p tcp --dport 8080 -j DROP
sudo ip6tables -A INPUT -p tcp --dport 8080 -j DROP

Rozbierzmy to na części:

  • -A INPUT - dopisz regułę na koniec łańcucha INPUT, czyli ruchu wchodzącego do serwera,
  • -p tcp - protokół TCP (dla UDP będzie -p udp),
  • --dport 8080 - port docelowy, ten, który blokujesz,
  • -j DROP - co zrobić z pakietem. W tym wypadku wyrzucić bez słowa. Nadawca nie dostaje żadnej odpowiedzi, co przy okazji utrudnia życie skanerom.

Otwieranie portu#

Na świeżym systemie łańcuch INPUT ma zwykle politykę ACCEPT, czyli wpuszcza jak leci. Jeśli jej nie zmieniałeś, to właściwie nie ma czego “otwierać”. Ale gdy masz już domyślne DROP (do tego dojdziemy za moment), port otwierasz tak:

sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 443 -j ACCEPT

No dobrze, a jak z iptables zrobić prawdziwy firewall? Taki, który domyślnie blokuje wszystko i przepuszcza tylko to, na co wyraźnie pozwolisz?

Kolejność jest tu wszystkim. Najpierw otwierasz porty, z których korzystasz, a absolutnym minimum jest SSH. Na Mikrusie serwer SSH w kontenerze nasłuchuje na 22, mimo że z zewnątrz łączysz się na 10000+ID:

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo ip6tables -A INPUT -p tcp --dport 22 -j ACCEPT

Następnie wpuszczasz ruch należący do połączeń, które już zostały nawiązane. Pominiesz to, a przestaną docierać choćby odpowiedzi z serwerów DNS i w ogóle cały ruch zwrotny:

sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
sudo ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

I dopiero na samym końcu domyślna polityka DROP:

sudo iptables -P INPUT DROP
sudo ip6tables -P INPUT DROP

Ustawisz DROP przed otwarciem SSH? W tej samej sekundzie tracisz dostęp do serwera. Bez ostrzeżenia. Na Mikrusie z tarapatów wyciągnie Cię MARIAN z panelu - puścisz nim polecenie naprawcze w rodzaju iptables -P INPUT ACCEPT, nawet jeśli SSH milczy jak zaklęte.

Ograniczenie do jednego adresu IP#

Port otwarty tylko dla jednego, wybranego adresu. Typowy scenariusz: baza danych dostępna wyłącznie z Twojego drugiego serwera:

sudo iptables -A INPUT -p tcp -s 81.15.20.33 --dport 3306 -j ACCEPT

W miejsce 81.15.20.33 wpisz adres, któremu dajesz przepustkę. Pamiętaj tylko, że taka reguła ma sens dopiero przy polityce DROP z poprzedniej sekcji, bo przy domyślnym ACCEPT i tak wszyscy wchodzą. Upewnij się też, że wyżej w łańcuchu nie ma ogólnego ACCEPT na ten sam port. iptables stosuje pierwszą pasującą regułę, więc kolejność robi tu kolosalną różnicę.

Usuwanie reguły#

Najprościej podać tę samą regułę co przy dodawaniu, tylko z -D zamiast -A:

sudo iptables -D INPUT -p tcp --dport 8080 -j DROP

Albo po numerze z iptables -L INPUT -n --line-numbers:

sudo iptables -D INPUT 3

Zapisywanie reguł na stałe#

A teraz pułapka, w którą wpada niemal każdy przynajmniej raz. Reguły dodane przez iptables i ip6tables znikają po restarcie kontenera. Wszystkie. Żeby przetrwały, potrzebujesz iptables-persistent:

sudo apt update
sudo apt install iptables-persistent

W trakcie instalacji pakiet zapyta, czy zapisać bieżące reguły IPv4 i IPv6. Na oba pytania odpowiadasz twierdząco. Jeśli później dodasz coś nowego, zapisujesz ręcznie:

sudo netfilter-persistent save

Reguły trafiają do /etc/iptables/rules.v4 i /etc/iptables/rules.v6, a przy starcie kontenera wczytują się same.

Najczęstsze błędy#

Odcięcie sobie SSH. Już o tym było, ale powtórzę, bo to najboleśniejszy błąd z całej listy: najpierw port SSH, dopiero potem DROP.

Reguły znikają po restarcie. Nie zostały zapisane przez iptables-persistent / netfilter-persistent save.

Po IPv4 port zablokowany, a z zewnątrz nadal widać. Zapomniane ip6tables. Na Mikrusie to całkiem realny problem, bo IPv6 nie przechodzi przez NAT tak jak IPv4.

Reguła niczego nie zmienia. Najpewniej wyżej w łańcuchu czai się szersza reguła, na przykład ogólny ACCEPT. iptables bierze pierwsze trafienie, a nie tę regułę, która pasuje najdokładniej.