Firewall a NAT
Firewall
Výkon a omezení
Už jsme rozebírali omezení a výkon conntracku a také fakt, že pravidla v řetězcích se zkouší od začátku do konce řetězce. Logicky tedy čím méně pravidel máme v jednotlivých řetězcích, tím rychlejší bude zpracování jednotlivého packetu a tím většího výkonu dosáhneme. Minimalizovat počet pravidel v řetězcích lze dvěma způsoby:
- Pro pravidla, která matchují velké množství konstant jednoho typu lze použít akcelerované datové struktury, kam se konstanty uloží a matchují se pak jedním pravidlem - nejznámější akcelerovaná struktura v Netfilteru je set, čili množina konstant - například IP adres, subnetů a nebo čísel portů.
- Pokud máme naopak složitá pravidla která matchují několik parametrů v konjunkci, lze si vzpomenout na převody logických formulí mezi normálními formami a uvědomit si, že z pravidel můžeme vytknout společné testy a vytvořit tak z pravidel strom uživatelem definovaných řetězců.
Pokud jde o akcelerované datové struktury - v iptables bylo jejich použití těžkopádné a vyžadovalo použití další utility ipset
na správu množin - odkážeme na manuál a na příklady:
V nftables jsou naopak množiny velmi pohodlné na použití. Dokonce se v předchozích příkladech už párkrát vyskytly, aniž bychom jim věnovali pozornost. V nftables jsou dokonce dvě možnosti definice a použití setů - anonymní sety jsou zkrátka konstanty napsané do složených závorek:
iifname {"vlan100", "vlan110"} udp dport bootps counter accept
ip6 saddr 2001:db8::5054:ff:fe93:e74b tcp dport {22, 5665} counter accept
ip saddr {192.168.1.100, 192.168.1.110, 192.168.1.120} counter drop
Druhou možností jsou pojmenované množiny. Ty mají tu výhodu, že je lze testovat odkazem ve více pravidlech a navíc je lze snadno upravovat pomocí utility nft
, aniž by se měnily samotná pravidla:
table ip filter {
set ssh_allowed {
type ipv4_addr
flags interval
elements = { 172.16.24.32, 192.168.224.192/28 }
}
chain INPUT {
type filter hook input priority 0;
policy drop;
ip saddr @ssh_allowed tcp dport 22 counter accept
}
}
Viditelnou nevýhodou je, že pojmenovaný set musíme explicitně definovat společně s typem a v našem příkladě, kde jsme chtěli používat jak subnety, tak jednotlivé IP adresy, tak jsme museli přidat flags interval
. To pochopitelně trochu komplikuje použití (ovšem proti iptables
a ipset
to je pořád zlepšení) ale také to optimalizuje vygenerování a použití setu.
Vedle toho má nftables něco jako preprocesor pravidel, který lze použít ke zkonstruování pravidel i samotného obsahu pojmenovaných i nepojmenovaných setů:
define gw = 192.168.1.1
define http_allowed = { $gw, 10.150.0.0/24, 10.250.0.250 }
table ip filter {
set ssh_allowed {
type ipv4_addr
flags interval
elements = { $gw, 172.16.24.32, 192.168.224.192/28 }
}
chain INPUT {
type filter hook input priority 0;
policy drop;
ip saddr $http_allowed tcp dport 80 counter accept
ip saddr @ssh_allowed tcp dport 22 counter accept
}
}
To, co se s iptables muselo řešit složitými skripty, které vlastně generovaly finální pravidla na základě maker a abstrakce, lze s nftables udělat přímo v konfiguraci. Společně s použitím setů to z nftables dělá velmi dobrého nástupce. Na závěr této sekce se hodí podotknout, že akcelerované datové struktury nekončí jen u setů. nftables mají například i slovníky nebo lépe mapy, které se hodí například k řízení překladu adres. Pro podrobnější vysvětlení přidáváme následující odkazy:
- https://wiki.nftables.org/wiki-nftables/index.php/Sets
- https://wiki.nftables.org/wiki-nftables/index.php/Maps
Když se teď ještě na chvíli zastavíme u možnosti přerovnat pravidla do stromu, je třeba předně podotknout, že to je možnost spíše teoretická. Přesto rozdělení do více řetězců smysl mnohdy dává a na to je třeba v první řadě definovat vlastní řetězec a nasměrovat do něj konkrétní packety. Opět si to předvedeme na příkladě v nftables:
table inet filter {
chain FORWARD {
type filter hook forward priority 0; policy drop;
ct state invalid counter drop
iifname "vlan100" counter accept
oifname "vlan100" jump office_in
iifname "vlan101" goto wifi_out
oifname "vlan101" goto wifi_in
}
chain office_in {
ct state {established, related} counter accept
# accept connection to servers
ip daddr {10.1.1.10, 10.1.1.11} tcp dport {22, 80, 443} counter accept
meta l4proto icmp counter accept
}
chain wifi_in {
ct state {established, related} counter accept
# accept connection to servers
ip daddr {192.168.1.100, 192.168.1.101, 192.168.1.102} tcp dport {80, 443} counter accept
meta l4proto icmp counter accept
}
chain wifi_out {
# IOT outgoing connections are restricted
ip saddr {192.168.1.10, 192.168.1.20, 192.168.1.30, 192.168.1.40} ip daddr != 10.1.1.10 counter drop
# allow all others
counter accept
}
}
V první řadě je třeba upozornit na to, že zatímco do řetězce FORWARD jsou packety vkládány na základě jeho navázání na forward hook pomocí: type filter hook forward priority 0; policy drop;
. V našem příkladě dále packety aktivně posíláme pomocí akce goto
nebo jump
do konkrétního námi-definovaného řetězce, pokud mají shodu s některým pravidlem v řetězci FORWARD s těmito akcemi. Rozdíl mezi jump
a goto
je ten, že jump
vrátí packet z podřetězce zpět za pravidlo s jump
, pokud v podřetězci nedojde ke shodě a rozhodnutí. Místo toho goto
skok zpět neprovede a pokud v podřetězci, do kterého se přešlo pomocí goto
, nebude shoda, tak se rovnou vykoná výchozí (policy) akce v nadřazeném řetězci.
Odkazy: