Netfilter

nftables

Podíváme se teď na ta samá pravidla pro nftables. To má výhodu, že nftables mají konfigurační soubor s pravidly /etc/nftables.conf a k tomu utilitu nft, která umí konfigurační soubor načíst pomoci nft -f /etc/nftables.conf .

Naše pravidla tedy budou vypadat takto:

table ip filter {
    chain INPUT {
        type filter hook input priority filter; policy drop;

        iifname "lo" counter accept
        ip saddr 192.168.1.10 counter drop
        iifname "eth0" meta l4proto tcp ip saddr 192.168.1.0/24 tcp dport {22,80,443} accept
        meta l4proto icmp counter accept
    }
}

Tato pravidla se čtou mnohem snáz, nicméně je tu několik koncepčních změn: v první řadě slovo counter vyjadřuje, že pro pravidlo, které má toto slovo před akcí (accept nebo drop) se bude počítat počet packetů a bytů v packetech, které měly s pravidlem shodu.

Další poměrně důležitá koncepční změna je meta l4proto icmp resp. meta l4proto tcp: nftables umožňují sice napsat ip protocol icmp a v tomto případě by to nevadilo. Problém vzniká u IPv6, kde v políčku Next Header může být hodnota pro ICMP a nebo tam může být hodnota identifikující následující IPv6 extension header. V tomto případě by se pak pravidlo ip6 nexthdr icmpv6 neshodovalo s packetem i když by za hlavičkou IPv6 extension header byl ICMPv6 datagram. A to se opravdu stává - Hop-by-Hop Extension Header se používá pro MLD - Multicast Listener Discovery. A když nefunguje MLD, nemusí fungovat ani Neighbor Discovery, takže nakonec nebude fungovat v podstatě nic.

Jestli má IPv6 nějakou chybu, která způsobuje jeho trochu pošramocenou pověst - že se záhadně rozbíjí a nikdo neví proč, tak tohle je přesně příklad věci, která funguje u IPv6 trochu složitěji, než u IPv4 a na kterou si člověk snadno naběhne a celé to začne dávat smysl až po delší chvíli čtení, studia a diskuzí, proč to stálo za to udělat trochu složitější, než u IPv4. Na druhou stranu nftables je moderní a zavedlo meta l4proto pro IPv4 i IPv6, aby se zjednodušila situace s výchozím a doporučované nastavení bylo správně pro IPv4 i pro IPv6.

Poté, co pravidla instalujeme do kernelu pomocí nft -f /etc/nftables.conf je můžeme snadno zobrazit i s aktuálními countery:

root@c1:~# nft list ruleset
table inet filter {
    chain input {
        type filter hook input priority filter; policy accept;
    }

    chain forward {
        type filter hook forward priority filter; policy accept;
    }

    chain output {
        type filter hook output priority filter; policy accept;
    }
}
table ip filter {
    chain INPUT {
        type filter hook input priority filter; policy drop;
        iifname "lo" counter packets 180 bytes 14996 accept
        ip saddr 192.168.1.10 counter packets 0 bytes 0 drop
        iifname "eth0" meta l4proto tcp ip saddr 192.168.1.0/24 tcp dport { 22,80,443} counter packets 0 bytes 0 accept
        meta l4proto icmp counter packets 1 bytes 93 accept
    }

    chain FORWARD {
        type filter hook forward priority filter; policy accept;
    }

    chain OUTPUT {
        type filter hook output priority filter; policy accept;
    }
}

Všimněte si, že pravidla v předchozích případech nebyla úplně praktická - nepovolovala například spojení navázaná z našeho serveru kamkoliv do Internetu. Přesně řečeno: V OUTPUT řetězci sice bylo vše povoleno, takže odchozí packety by odešly. Ale odpovědi od serverů v Internetu by byly zahozeny, ledaže by matchovaly jedno z povolujících pravidel a to v našem případě znamená jen jedno pravidlo, které povoluje spojení na HTTP, HTTPS a SSH server. Můžeme to zachránit bezstavově tak, že pro protokol TCP povolíme všechny příchozí packety (TCP segmenty), krom těch, které navazují nové spojení a tedy mají nastaven flag SYN=1. To uděláme v iptables takto:

iptables -A INPUT -p tcp ! --syn -j ACCEPT

A pro nftables to bude v tabulce ip filter v řetězci INPUT pravidlo:

meta l4proto tcp tcp flags & (fin|syn|rst|ack) != syn counter accept

Jenže to sebou přece jen přináší problémy: z hlediska bezpečnosti to je nedokonalé. Umožňuje to útočníkům posílat TCP segmenty mimo normální pořadí, napadat běžící spojení a zkoušet se do nich vmísit a vůbec celé rozlišení, co jsou odpovědi na spojení navázané ven a co je pokračování spojení zvenčí, je od třetího packetu prakticky nemožné. U protokolu UDP nic, jako SYN flag není, takže tam jsou možnosti ještě víc omezené - prakticky můžete jen matchovat kombinací zdrojových a cílových adres a portů a jen podle nich datagram buď propustíte a nebo zahodíte.

Dejme tomu, že v příchozím směru by to bezstavově s protokolem TCP ještě šlo, za cenu složitějších pravidel a ústupků z bezpečnosti. V odchozím směru je to ale problém - tam už opravdu nerozlišíte, co je další segment běžícího spojení od vás ven a co je odpověď na nově navázané spojení zvenčí. Nemluvě o NATu, což je disciplína, ve které Linux vyniká, ovšem jen díky stavovému zpracování packetů - conntracku.

Zatím jsme se zabývali jen nejjednodušším případem - filtrování packetů v tabulce filter v řetězci INPUT. Netfilter má však mnoho výchozích řetězců a vedle toho umožňuje definovat vlastní řetězce uživateli. Průchod packetu firewallem je mnohafázový proces a rozebereme jej podrobněji v závěrečné kapitole.

Odkazy: