Przejdź do treści
Lekcja 73 z 117

Kurs › Moduł 8 › Lekcja 73

Otwieranie i zamykanie portów

⏱ ok. 13 min czytania· moduł 8: Sieć

Po tej lekcji: kursant włącza zaporę na serwerze tak, żeby nie odciąć sam siebie, otwiera i zamyka porty w ufw (Ubuntu) i firewalld (RHEL), rozumie strefy, różnicę między ustawieniem chwilowym a stałym oraz wie, że pod spodem i tak pracuje nftables.

Wstęp

terminal z sesją SSH, ktoś wpisuje sudo ufw enable, Enter… kursor miga… miga… i nic

Piątek, szesnasta czterdzieści. Admin robi porządki. „Włączę jeszcze zaporę, będzie bezpieczniej”. Wpisuje jedną komendę, potwierdza, wraca do terminala, a terminal milczy. Nowe połączenie? Czas minął. Serwer stoi w serwerowni na drugim końcu Polski, a jedyną drogą do niego było właśnie SSH, które przed chwilą sam zamknął.

Zapora to najlepszy przyjaciel serwera i najszybszy sposób, żeby się z niego wykluczyć. Dziś nauczysz się otwierać i zamykać porty tak, żeby zawsze zostawić sobie otwarte drzwi. Na Ubuntu przez ufw, na Red Hacie przez firewalld, a na końcu zajrzymy do maszynowni, czyli do nftables.

Ochroniarz z listą okienek

Pamiętasz urząd z portami? Port to numer okienka. Serwer WWW siedzi przy okienku 80 i 443, SSH przy 22, baza danych przy 3306 albo 5432. Do tej pory do każdego okienka mógł podejść każdy z ulicy.

Zapora sieciowa, po angielsku firewall, to ochroniarz w drzwiach urzędu z listą. Na liście jest napisane: „do okienka 22 wpuszczać, do 80 wpuszczać, do reszty nikogo”. Ochroniarz nie wie, co się dzieje w okienkach. Nie zna haseł, nie czyta dokumentów. Patrzy tylko, kto przyszedł, skąd i do którego okienka idzie.

Ważne: zapora i nasłuchiwanie to dwie różne rzeczy. ss z poprzedniej lekcji mówi, które okienka są obsadzone. Zapora mówi, do których okienek ochroniarz w ogóle kogoś wpuści. Port jest naprawdę dostępny z zewnątrz tylko wtedy, gdy oba warunki są spełnione: ktoś siedzi w okienku i ochroniarz przepuszcza.

W Linuksie tym ochroniarzem jest jądro systemu, a dokładniej jego część o nazwie netfilter. Ty z nim nie rozmawiasz bezpośrednio, tylko przez tłumaczy:

  • ufw, czyli Uncomplicated Firewall, „nieskomplikowana zapora”. Domyślny tłumacz na Ubuntu. [Ubuntu]
  • firewalld z komendą firewall-cmd. Domyślny na RHEL, Rocky, Almie i Fedorze. [RHEL]
  • nftables, komenda nft. Bezpośredni język jądra, w którym obaj powyżsi i tak zapisują swoje reguły.
  • iptables. Stary język, który wciąż spotkasz w poradnikach z 2015 roku i w skryptach, których nikt nie odważył się ruszyć.

Zasada numer jeden: na jednym serwerze jeden tłumacz. Włączony naraz ufw i firewalld to dwóch ochroniarzy z różnymi listami, którzy kłócą się w drzwiach, a petent stoi w deszczu.

ufw: zapora po ludzku [Ubuntu]

Na Ubuntu ufw jest zainstalowany, ale domyślnie wyłączony. Sprawdźmy:

sudo ufw status
Status: inactive

Teraz najważniejszy moment całej lekcji. Zanim włączysz zaporę, pozwól na SSH. Zawsze. Nawet jak siedzisz przy fizycznej klawiaturze, bo to odruch, który ma wejść w krew jak zapinanie pasów.

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
Rules updated
Rules updated

OpenSSH to gotowy profil aplikacji: zestaw portów, który pakiet zostawił w /etc/ufw/applications.d. Listę profili zobaczysz przez sudo ufw app list. Zamiast profilu możesz napisać wprost sudo ufw allow 22/tcp. Dopiero teraz włączamy:

sudo ufw enable

Jeśli robisz to przez SSH, ufw sam się zaniepokoi:

Command may disrupt existing ssh connections. Proceed with operation (y|n)?

„Ta komenda może przerwać istniejące połączenia SSH”. To nie jest grzecznościowe pytanie. To ostatni moment, żeby sprawdzić, czy na liście jest SSH. Sprawdzamy, co jest na liście:

sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere

Linijka Default to polityka domyślna, czyli co ochroniarz robi z kimś, kogo nie ma na liście: przychodzących nie wpuszcza (deny incoming), wychodzących wypuszcza (allow outgoing). Tak jak w porządnym biurowcu: wyjść może każdy, wejść tylko z przepustką. Na prawdziwym serwerze z IPv6 zobaczysz jeszcze drugi komplet linijek z dopiskiem (v6), bo ufw pilnuje od razu obu wersji adresów.

Kilka reguł, które przydają się co tydzień:

sudo ufw allow from 10.0.0.0/24 to any port 3306 proto tcp   # baza tylko z sieci biurowej
sudo ufw limit 22/tcp                                        # SSH z hamulcem na zgadywaczy haseł
sudo ufw deny 23/tcp                                         # telnet: nie, dziękuję

limit to ochroniarz, który zapamiętuje twarze: jak ktoś z jednego adresu próbuje wejść sześć razy w pół minuty, przestaje go wpuszczać. Na roboty zgadujące hasła jak znalazł.

Usuwanie reguł. Najwygodniej po numerze:

sudo ufw status numbered
     To                         Action      From
     --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 80/tcp                     DENY IN     Anywhere
[ 3] 8081/tcp                   ALLOW IN    10.0.0.20
sudo ufw delete 2

Albo przez powtórzenie reguły ze słowem delete: sudo ufw delete allow 80/tcp.

Ostatnie dwie komendy na awaryjne sytuacje: sudo ufw disable wyłącza zaporę, a sudo ufw reset kasuje wszystkie reguły, robiąc wcześniej kopię plików z regułami z datą w nazwie.

firewalld: strefy i dwie pamięci [RHEL]

Na Red Hacie ochroniarz nazywa się firewalld i jest włączony od razu po instalacji. Działa jak usługa w tle, a rozmawiasz z nim przez firewall-cmd.

sudo firewall-cmd --state
sudo firewall-cmd --list-all
running
public (default, active)
  target: default
  interfaces: enp1s0
  services: cockpit dhcpv6-client ssh
  ports:
  masquerade: no
  forward-ports:
  rich rules:

Dwie nowe rzeczy. Pierwsza to strefa, po angielsku zone. Wyobraź sobie, że budynek ma kilka wejść: od ulicy, od parkingu firmowego i z zaplecza. Przy każdym wejściu stoi inny ochroniarz z inną listą. Na wejściu od ulicy (public) wpuszczają tylko na SSH. Na wejściu z zaplecza (trusted) wpuszczają wszystkich jak leci. Karta sieciowa albo adres źródłowy jest przypisany do jednej strefy i to decyduje, która lista obowiązuje.

sudo firewall-cmd --get-zones
sudo firewall-cmd --get-active-zones
block dmz drop external home internal nm-shared public trusted work
public (default)

Na start wystarczą trzy: public to domyślna, ostrożna strefa dla zwykłej karty. trusted wpuszcza wszystko, więc trafiają tam tylko adresy, którym ufasz jak własnej matce. drop po cichu wyrzuca wszystko, co przychodzi. Przykład: wszystko z komputera admina ma przechodzić bez pytań:

sudo firewall-cmd --zone=trusted --add-source=10.0.0.20/32

Druga nowa rzecz: usługi. Zamiast pamiętać numery portów, mówisz firewalld nazwę: http, https, ssh, mysql. U mnie firewall-cmd --get-services zna ponad dwieście takich nazw. Podgląd jednej:

sudo firewall-cmd --info-service=http
http
  ports: 80/tcp

Otwieramy stronę WWW:

sudo firewall-cmd --add-service=http
sudo firewall-cmd --list-services
success
dhcpv6-client http ssh

Działa. I tu jest pułapka, na którą wpada każdy.

sudo firewall-cmd --reload
sudo firewall-cmd --list-services
success
dhcpv6-client ssh

http zniknął. Bo firewalld ma dwie pamięci. Runtime, czyli pamięć bieżąca: działa od razu, ale znika przy przeładowaniu i restarcie. Permanent, czyli zapis w plikach: przetrwa restart, ale nie działa, dopóki nie przeładujesz. Jak karteczka przyklejona na szybie dyżurki kontra wpis do regulaminu, który wejdzie w życie od jutra.

Poprawny sposób na stałe:

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
sudo firewall-cmd --list-services
success
success
dhcpv6-client http ssh

Port bez nazwy usługi otwierasz przez --add-port, a jak już się nabawiłeś reguł w runtime i działają, możesz je wszystkie zapisać naraz:

sudo firewall-cmd --add-port=8081/tcp
sudo firewall-cmd --runtime-to-permanent
sudo firewall-cmd --permanent --list-ports
success
success
8081/tcp

Zamykanie to te same komendy z remove zamiast add: --remove-service=http, --remove-port=8081/tcp, z --permanent albo bez.

[Ubuntu] firewalld da się zainstalować także na Ubuntu (sudo apt install firewalld), ale wtedy wyłącz ufw. Dwóch ochroniarzy w jednych drzwiach to przepis na kabaret.

nftables: maszynownia

Pod oboma tłumaczami pracuje to samo: nftables. ufw i firewalld to wygodne formularze, które na końcu wypełniają reguły w jądrze. Czasem jednak warto zajrzeć do maszynowni, bo tylko tam widać całą prawdę:

sudo nft list ruleset

Na serwerze z ufw zobaczysz kilkaset linijek. Dla porównania własna, prosta zapora w pliku:

cat zapora.nft
#!/usr/sbin/nft -f
flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;
        ct state established,related accept
        iif "lo" accept
        tcp dport 22 accept
        tcp dport { 80, 443 } accept
        icmp type echo-request accept
    }
    chain forward {
        type filter hook forward priority 0; policy drop;
    }
    chain output {
        type filter hook output priority 0; policy accept;
    }
}

Czytamy po kolei. table inet filter to segregator na reguły, inet znaczy IPv4 i IPv6 naraz. chain input to lista dla ruchu, który przychodzi do serwera. policy drop to domyślnie: nie wpuszczać. Potem wyjątki: połączenia już nawiązane (ct state established,related), żeby odpowiedzi na Twoje własne zapytania mogły wrócić. Ruch od samego siebie (lo). SSH. Strona WWW. Ping.

Najpierw sprawdzenie składni, potem wczytanie:

sudo nft -c -f zapora.nft && echo "składnia OK"
sudo nft -f zapora.nft

Regułę można też dopisać w locie i usunąć po numerze, który nft nazywa uchwytem, handle:

sudo nft add rule inet filter input tcp dport 8081 accept
sudo nft -a list chain inet filter input
sudo nft delete rule inet filter input handle 10

Na stałe reguły trzyma plik /etc/nftables.conf [Ubuntu] albo /etc/sysconfig/nftables.conf [RHEL], wczytywany przez usługę nftables. Ale uwaga: jak używasz ufw albo firewalld, nie mieszaj im w ich tabelach ręcznie. Przy następnym przeładowaniu i tak nadpiszą Twoje poprawki.

iptables: język, który nie chce umrzeć

Starszy brat. Spotkasz go w każdym poradniku sprzed kilku lat:

sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -L INPUT -n -v --line-numbers
sudo iptables -D INPUT 2
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1        0     0 ACCEPT     6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80
2        0     0 DROP       6    --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:23

-A dopisuje na koniec łańcucha, -I wstawia na początek, -D usuwa, -L wypisuje. Ciekawostka:

iptables -V
iptables v1.8.10 (nf_tables)

To (nf_tables) znaczy, że dzisiejsze iptables to już tylko nakładka, która tłumaczy stare komendy na nftables. Red Hat w RHEL 9 oznaczył tę nakładkę jako przestarzałą. Czytać iptables umieć trzeba, pisać nowe reguły lepiej przez ufw, firewalld albo nft.

Jak to działa pod spodem

Każdy pakiet, który dociera do karty sieciowej, przechodzi przez jądro jak przez lotnisko z bramkami. Netfilter to zestaw takich bramek, po angielsku hooków: prerouting zaraz po wejściu, input dla pakietów do tego serwera, forward dla przejezdnych, output dla wychodzących, postrouting tuż przed wyjściem. Do każdej bramki można podpiąć łańcuch reguł. Reguły sprawdzane są od góry, pierwsza pasująca decyzja kończy sprawę: accept, drop albo reject.

Teraz najsprytniejsza część: conntrack, czyli śledzenie połączeń. Jądro pamięta każde nawiązane połączenie. Dlatego zapora nie musi mieć reguły dla odpowiedzi: jak Twój serwer zapytał o coś świat, odpowiedź jest established i przechodzi. I dlatego włączenie zapory zwykle nie zrywa już trwającej sesji SSH, tylko blokuje nowe. Admin z haka siedział w otwartej sesji jeszcze przez chwilę szczęśliwy, jak bohater kreskówki, który wybiegł za krawędź urwiska i jeszcze nie spojrzał w dół.

Samo ufw to program w Pythonie, który z plików w /etc/ufw generuje reguły. Widać to po nazwach łańcuchów:

sudo iptables -S | grep ufw-user-input
-A ufw-user-input -p tcp -m tcp --dport 22 -m comment --comment "'dapp_OpenSSH'" -j ACCEPT
-A ufw-user-input -p tcp -m tcp --dport 80 -j DROP

firewalld robi to samo, tylko jako usługa z własnymi plikami: domyślne strefy i usługi w /usr/lib/firewalld, Twoje zmiany z --permanent w /etc/firewalld.

Ściąga flag

KomendaFlagaCo robi
ufwallow 80/tcpwpuszcza ruch na port 80 TCP
ufwallow from 10.0.0.0/24 to any port 3306 proto tcpwpuszcza tylko z podanej sieci
ufwdeny / rejectblokuje po cichu / z odpowiedzią „odmowa”
ufwlimit 22/tcpwpuszcza, ale hamuje zbyt częste próby
ufwstatus verbose / status numberedpokazuje reguły z polityką / z numerami
ufwdelete Nusuwa regułę o numerze N
ufwenable / disable / resetwłącza / wyłącza / kasuje wszystkie reguły
firewall-cmd--add-service=httpotwiera usługę (runtime)
firewall-cmd--add-port=8081/tcpotwiera port (runtime)
firewall-cmd--permanentzapisuje zmianę w plikach zamiast w pamięci
firewall-cmd--reloadwczytuje reguły z plików, kasuje runtime
firewall-cmd--runtime-to-permanentzapisuje bieżące reguły na stałe
firewall-cmd--list-allwszystko o strefie
firewall-cmd--zone= / --get-active-zoneswybiera strefę / pokazuje aktywne
nftlist rulesetwypisuje wszystkie reguły jądra
nft-c -f pliksprawdza składnię pliku bez wczytywania
nft-apokazuje uchwyty reguł (do usuwania)
iptables-L -n -v --line-numberswypisuje reguły z licznikami i numerami
iptables-A / -I / -Ddopisuje / wstawia na początek / usuwa

⚠ Typowy błąd

Zamknięcie sobie drzwi przed nosem. Pokażę to na żywo na dwóch maszynach wirtualnych. [na ekranie: dwa terminale obok siebie] W pierwszej, przez SSH, włączam zaporę na siłę, bez reguły dla SSH:

sudo ufw --force enable
echo "jeszcze żyję"
Firewall is active and enabled on system startup
jeszcze żyję

Obecna sesja żyje, bo conntrack pamięta to połączenie. A teraz próbuję połączyć się drugi raz:

ssh -o ConnectTimeout=5 kuba@10.0.0.10
ssh: connect to host 10.0.0.10 port 22: Connection timed out

Po zamknięciu bieżącej sesji nie ma już powrotu. Ratunek? Póki masz otwartą sesję, natychmiast sudo ufw allow OpenSSH. Jak już ją zamknąłeś, zostaje konsola: panel dostawcy chmury, konsola maszyny wirtualnej, KVM w serwerowni albo telefon do kolegi, który jest najbliżej serwera i ma w sobie pokłady wybaczenia.

Trzy zasady, które to załatwiają:

  1. Najpierw allow dla SSH, potem enable.
  2. Przy zmianach zapory trzymaj drugą sesję SSH otwartą i z niej sprawdzaj, czy nowe połączenie przechodzi.
  3. Ćwicz na maszynie wirtualnej, do której masz konsolę.

✎ Zadanie

Na maszynie wirtualnej z Ubuntu: zezwól na SSH, włącz ufw, otwórz port 80 tylko dla jednego adresu IP (na przykład Twojej drugiej maszyny) i pokaż sudo ufw status numbered. Potem usuń regułę dla portu 80 po numerze. Na maszynie z RHEL, Rocky albo Almą: otwórz na stałe usługę https i port 8080/tcp, przeładuj zaporę i udowodnij komendą --list-all, że po --reload obie reguły zostały. Z drugiej maszyny sprawdź nc -zv, czy port faktycznie przechodzi.

Pokaż rozwiązanie

Ubuntu. Najpierw SSH, potem zapora, potem port 80 tylko dla jednego adresu:

sudo ufw allow OpenSSH
sudo ufw allow from 10.0.0.10 to any port 80 proto tcp
sudo ufw enable
sudo ufw status numbered
     To                         Action      From
     --                         ------      ----
[ 1] OpenSSH                    ALLOW IN    Anywhere
[ 2] 80/tcp                     ALLOW IN    10.0.0.10
sudo ufw delete 2
Deleting:
 allow from 10.0.0.10 to any port 80 proto tcp
Proceed with operation (y|n)? y
Rule deleted

RHEL, Rocky, Alma. --permanent, a potem przeładowanie, bo bez niego reguły leżą w regulaminie, ale ochroniarz jeszcze go nie przeczytał:

sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all
  services: cockpit dhcpv6-client http https ssh
  ports: 8080/tcp

Jeśli po --reload czegoś brakuje, to znaczy, że gdzieś zgubiłeś --permanent. Nie martw się, każdy admin zgubił go co najmniej raz. Większość w piątek.

Co dalej

Ochroniarz już stoi w drzwiach. W następnej lekcji zrobimy z serwera skrzyżowanie: routing, przekazywanie pakietów, NAT, przekierowanie portów na inną maszynę oraz łączenie kart sieciowych w jedną, mocniejszą.