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:

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: