Otwieranie i zamykanie portów
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]firewalldz komendąfirewall-cmd. Domyślny na RHEL, Rocky, Almie i Fedorze. [RHEL]nftables, komendanft. 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 statusStatus: inactiveTeraz 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/tcpRules updated
Rules updatedOpenSSH 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 enableJeś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 verboseStatus: 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 AnywhereLinijka 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.20sudo ufw delete 2Albo 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-allrunning
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-zonesblock 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/32Druga 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=httphttp
ports: 80/tcpOtwieramy stronę WWW:
sudo firewall-cmd --add-service=http
sudo firewall-cmd --list-servicessuccess
dhcpv6-client http sshDziała. I tu jest pułapka, na którą wpada każdy.
sudo firewall-cmd --reload
sudo firewall-cmd --list-servicessuccess
dhcpv6-client sshhttp 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-servicessuccess
success
dhcpv6-client http sshPort 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-portssuccess
success
8081/tcpZamykanie 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 rulesetNa 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.nftRegułę 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 10Na 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 2Chain 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 -Viptables 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 DROPfirewalld 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
| Komenda | Flaga | Co robi |
|---|---|---|
ufw | allow 80/tcp | wpuszcza ruch na port 80 TCP |
ufw | allow from 10.0.0.0/24 to any port 3306 proto tcp | wpuszcza tylko z podanej sieci |
ufw | deny / reject | blokuje po cichu / z odpowiedzią „odmowa” |
ufw | limit 22/tcp | wpuszcza, ale hamuje zbyt częste próby |
ufw | status verbose / status numbered | pokazuje reguły z polityką / z numerami |
ufw | delete N | usuwa regułę o numerze N |
ufw | enable / disable / reset | włącza / wyłącza / kasuje wszystkie reguły |
firewall-cmd | --add-service=http | otwiera usługę (runtime) |
firewall-cmd | --add-port=8081/tcp | otwiera port (runtime) |
firewall-cmd | --permanent | zapisuje zmianę w plikach zamiast w pamięci |
firewall-cmd | --reload | wczytuje reguły z plików, kasuje runtime |
firewall-cmd | --runtime-to-permanent | zapisuje bieżące reguły na stałe |
firewall-cmd | --list-all | wszystko o strefie |
firewall-cmd | --zone= / --get-active-zones | wybiera strefę / pokazuje aktywne |
nft | list ruleset | wypisuje wszystkie reguły jądra |
nft | -c -f plik | sprawdza składnię pliku bez wczytywania |
nft | -a | pokazuje uchwyty reguł (do usuwania) |
iptables | -L -n -v --line-numbers | wypisuje reguły z licznikami i numerami |
iptables | -A / -I / -D | dopisuje / 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.10ssh: connect to host 10.0.0.10 port 22: Connection timed outPo 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ą:
- Najpierw
allowdla SSH, potemenable. - Przy zmianach zapory trzymaj drugą sesję SSH otwartą i z niej sprawdzaj, czy nowe połączenie przechodzi.
- Ć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.10sudo ufw delete 2Deleting:
allow from 10.0.0.10 to any port 80 proto tcp
Proceed with operation (y|n)? y
Rule deletedRHEL, 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/tcpJeś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ą.