Hlavní navigace

ipset: odlehčete přetíženým iptables

14. 7. 2014
Doba čtení: 8 minut

Sdílet

Linuxový firewall je dobrý sluha, ale zlý pán. Pokud ho ovládáte, zjednoduší vám v mnoha ohledech život. Pokud v něm naopak plavete, můžete se dočkat probdělých nocí a bolesti hlavy. Jedním z méně známých řešení je ipset, který umožňuje pravidla iptables zredukovat a odlehčit firewallu i jeho správci.

Prolog: o pravidlech a řetězcích

Firewall není žádná tajemná magie, přesto je to věc, která linuxové začátečníky děsí. Ti se často naučí několik základních postupů a u nich pak skončí. Výsledkem je pak často ohromná sada pravidel nacpaných na jednu hromadu, přetížený firewall, nízký výkon a stížnosti na fórech: „Je to nějaké pomalé a to tam mám jen dvacet tisíc pravidel!“

Problém je v takovém případě v sekvenčnosti iptables. Ty totiž obsahují sadu pravidel ve formátu pravidlo – akce. Pokud dorazí na síťové rozhraní paket, je postupně zkoumán a porovnáván se všemi vyjmenovanými pravidly. Jakmile je některé pravidlo splněno, akce se vykoná, paket se třeba propustí dále a je hotovo. Pokud paket žádnému pravidlu nevyhověl, použije se výchozí politika (policy).

Potíž nastává, když admin z veřejného blacklistu vydoluje několik tisíc adres, vygeneruje z nich několik tisíc pravidel a ty nalije na jednu hromadu. Firewall pak u každého paketu zkoumá, zda náhodou nepřichází z první IP adresy, druhé IP adresy, třetí IP adresy… pětitisící IP adresy. S výkonem to může udělat nehezké věci. Viděl jsem například firewall s tisíci pravidel tohoto typu:

# iptables -A INPUT -i eth0 -p tcp -s 192...

Takové pravidlo se aplikuje pouze na pakety, které přichází z rozhraní eth0 (v tomto případě z internetu). Ovšem testuje se na něj zbytečně každý paket, který se objeví na libovolném rozhraní. I pakety pocházející z vnitřní sítě se v takovém případě tisíckrát zkontrolují, zda nepochází z internetu. Zbytečně.

Rozhodně existuje několik věcí, které je rozumné v takové situaci udělat jinak a lépe. Začátečníci o nich však často neví. Například je dobré nahoru přidat pravidlo, které povolí pakety náležící už k existujícím spojením. Ta už prověřením jednou prošla a je obvykle zbytečné kontrolovat každý paket zvlášť.

# iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

Druhou zásadní věcí, na kterou chci na začátku upozornit, jsou řetězce (anglicky chains). Ty umožňují plochou strukturu pravidel rozdělit do stromu a dojít k cíli výrazně rychleji. V hlavním řetězci tak necháte jen několik málo pravidel:

# iptables -A INPUT -p tcp --dport 22 -j sshcko

Vše ostatní se pak odehrává v jiném řetězci. Není tak nutné na pravidla týkající se SSH testovat pakety, které přicházejí třeba na port 80. Takto je možné logicky členit a drobit a z tisíců pravidel pro každý paket udělat třeba jen několik desítek.

Přečtěte si: seriál Vše o iptables

Sady pomocí ipset

Velmi častým úkolem firewallu je filtrovat provoz podle zdrojových IP adres. Kvůli už zmíněné sekvenčnosti iptables je ale poměrně nevýhodné cpát jednotlivé adresy do samostatných pravidel. Toho si naštěstí všiml Jozsef Kadlecsik, který před lety vymyslel a naprogramoval ipset – modul pro iptables umožňující uložení a porovnávání velkých bloků (nejen) IP adres.

Hodí se, pokud chcete:

  • uložit velké množství adres a jedním pravidlem proti nim porovnávat
  • dynamicky tento seznam měnit bez nutnosti zasahovat přímo do iptables
  • nahradit desítky řetězců plných pravidel jedním pravidlem

Co je to tedy ipset? Jde o rozšíření iptables, které umožňuje vytvářet sady (sets) různých síťových informací (IP, MAC, port…). V paměti si tak správce vytváří malé databáze, proti kterým je pak možné velmi rychle porovnávat. Jsou vytvořeny pomocí paměťových bitmap nebo hashovacích tabulek, takže práce s nimi je velmi rychlá. Přehled různých variant sad vám pomůže pochopit princip.

bitmap:ip
Používá kus paměti, ve kterém jeden bit reprezentuje jednu IP adresu z předem zvoleného rozsahu (maximálně /16). Takto je možné uložit až 65535 položek.
bitmap:ip,mac
Používá kus paměti, ve kterém každých 8 bytů reprezentuje jednu kombinaci IP adresy a MAC adresy. Takto je možné uložit až 65535 adres.
bitmap:port
Používá kus paměti, ve které jeden bit reprezentuje jeden TCP/UDP port. Takto je možné uložit až 65535 portů.
hash:ip
Používá hash k ukládání IP adres. V případě kolize se automaticky zdvojnásobí velikost hashe.
hash:net
Také používá hash, ovšem k ukládání bloků adres pomocí CIDR.
hash:ip,port
Umožňuje uložit kombinaci IP adresy, protokolu a portu. Podporuje protokoly TCP, SCTP, UDP, UDPLITE, ICMP a ICMPv6.
hash:ip,port,ip
Umožňuje uložit trojici IP adresa, port, IP adresa.
hash:ip,port,net
Umožňuje uložit trojici IP adresa, port, síťový rozsah.
hash:net,port
Umožňuje uložit pár síťový rozsah a port.
hash:net,iface
Umožňuje uložit síťový rozsah a rozhraní.
list:set
Umožňuje uložit kombinaci jiných sad ipset.

Pokud začnete využívat ipset, založíte si novou sadu informací, naplníte ji a poté už jen pomocí iptables zjišťujete, zda IP adresa zkoumaného paketu patří do vámi vytvořené sady. Velkou výhodou také je, že samotné sady můžete velmi rychle editovat, aniž byste zasahovali přímo do pravidel iptables.

Detaily k použití jednotlivých typů sad najdete v manuálové stránce.

Jak to funguje?

Pokud chcete ipset nasadit, není nic jednoduššího než nainstalovat stejnojmenný balíček. Tím získáte utilitu k ovládání a můžete začít.

Nejprve si zkusíme založit sadu s názvem „první“ a přidat do ní hned jednu adresu.

# ipset create prvni hash:ip
# ipset add prvni 10.1.2.3

Přímo pomocí ipset můžeme testovat, zda některá adresa do sady patří nebo ne:

# ipset test prvni 192.168.1.1
192.168.1.1 is NOT in set prvni.
# ipset test prvni 10.1.2.3
10.1.2.3 is in set prvni.

Můžeme si také přímo vypsat, co je obsahem naší sady. Dozvíme se například i to, kolik paměti tato konkrétní sada zabírá:

# ipset list prvni
Name: prvni
Type: hash:ip
Revision: 2
Header: family inet hashsize 1024 maxelem 65536
Size in memory: 16520
References: 0
Members:
10.1.2.3

Stejně tak je možné velmi snadno další adresy přidávat nebo odebírat:

# ipset add prvni 192.168.1.100
# ipset del prvni 10.1.2.3

Pokud už máme takto založenou a naplněnou sadu, stačí přidat související pravidlo do iptables:

# iptables -A INPUT -m set --match-set první src -j DROP

Toto jedno pravidlo porovná adresu příchozího paketu se sadou ipset a pokud se v ní adresa nachází, paket zahodí. Mimochodem zavedením tohoto pravidla se zvýší počítadlo referencí ve výpisu  ipset list.

Zkusíme ještě jeden příklad. Řekněme, že máme v práci několik kolegů, kteří zneužívají připojení k síti a šéf rozhodl, že tito vybraní jedinci se budou moci připojit jen k omezené sadě serverů. Vytvoříme si tedy dvě sady s adresami klientů a serverů:

# ipset create nezbedove hash:ip
# ipset create povolene hash:ip
# ipset add nezbedove 192.168.1.8
# ipset add nezbedove 192.168.1.32
# ipset add nezbedove 192.168.1.104
# ipset add nezbedove 192.168.1.121
# ipset add povolene zakaznik1.cz
# ipset add povolene zakaznik2.cz
# ipset add povolene posta.firma.cz
# ipset add povolene intranet.firma.cz

Nakonec přidáme do iptables pravidlo, které zahodí všechny pakety od nezbedů, pokud ovšem nemíří na některou z povolených služeb:

# iptables -I FORWARD -m set --match-set nezbedove src -m set ! --match-set povolene dst -j DROP

Výhodou je, že pak můžeme krásně přidávat nebo odebírat nezbedy, stejně jako jim můžeme přihazovat další schválené služby. Nemusíme už přitom vůbec zasahovat do pravidel firewallu.

Stavíme vlastní fail2ban

Služba fail2ban je mezi správci serverů hodně populární. Dokáže hlídat neúspěšné pokusy o přihlášení ke službám a automaticky na firewallu odříznout boty, kteří zkouší štěstí při hádání hesel. To je ideální pole pro ipset, protože firewall pak potřebuje kontrolovat příchozí spojení podle velkého seznamu zablokovaných IP adres.

Pokud používáte ipset, vlastně už žádný fail2ban nepotřebujete, protože vše za vás zvládne linuxové jádro. Je totiž možné adresy do sad automaticky přidávat i je nechat časem zase automaticky vypadnout. Což je přesně to, co dělá fail2ban.

Poznámka: Níže uvedené řešení pracuje na L3, takže na rozdíl od skutečného fail2ban nerozlišuje úspěšné a neúspěšné pokusy. Je třeba na to myslet.

Nejprve vytvoříme novou sadu, které rovnou přidáme timeout. Ten bude výchozí pro všechny přidané položky a po jeho vytikání položka ze sady vypadne. Pokud bychom chtěli pro některou položku vložit delší timeout nebo ji nechat v sadě navždy ( timeout 0), stačí při vkládání položky opět přidat parametr  timeout.

# ipset create zakazane hash:ip timeout 86400

Teď je potřeba zavést celkem tři pravidla do iptables. První propustí pakety patřící k již existujícím spojením, druhé zahodí pakety z blokovaných adres podle ipset a třetí bude počítat limit nových spojení a v případě jeho překročení přidá IP adresu do sady. V praxi to bude vypadat takto:

# iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A INPUT -m set --match-set zakazane src -j DROP
# iptables -A INPUT -p tcp --dport 22 -m hashlimit --hashlimit-above 5/min --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name SSH-bruteforce -j SET --add-set zakazane src

Možností je celá řada, můžeme si například vytvořit sadu povolených portů a pokud se někdo pokusí připojit k jinému portu, bude automaticky přidán na blacklist.

Uložení a načtení pravidel

Stejně jako u iptables si ani v případě ipsetu jádro při restartu nikam neukládá stav z paměti. Pokud tedy utility v uživatelském prostoru nějak samy nezajistí uložení, o pravidla restartem přijdeme. Některé distribuce jsou na ukládání připravené, do jiných se připravuje. Vše si lze snadno zařídit ve vlastní režii, protože utilita ipset nabízí parametry pro export a import stavu.

Parametr save vypíše na standardní výstup příkazy obnovující všechny sady. Pokud ještě přidáme jméno konkrétní sady, vypíše se pouze ta.

root_podpora

# ipset save
create prvni hash:ip family inet hashsize 1024 maxelem 65536
add prvni 192.168.1.30
add prvni 192.168.1.20
add prvni 192.168.1.10
create druha hash:net family inet hashsize 1024 maxelem 65536
add druha 172.0.0.0/12
add druha 192.0.0.0/16
add druha 10.0.0.0/8

# ipset save druha > ipset.druha

Pro obnovení slouží parametr restore, který zase na standardním vstupu poslouchá sled příkazů. Tady už neuvádíme název sady, jak vidíte výše, je součástí výpisu.

# ipset restore < ipset.druha

Užitečná poznámka na závěr: Pokud chcete vyprázdnit sadu, použijte ipset flush název, případně vynechejte název a vyprázdní se všechny. Pro smazání použijte stejným způsobem ipset destroy. Jádro vám nedovolí zlikvidovat sadu, pokud na ni vede nějaká reference (počítadlo je nenulové).

Další informace

Byl pro vás článek přínosný?

Autor článku

Petr Krčmář pracuje jako šéfredaktor serveru Root.cz. Studoval počítače a média, takže je rozpolcen mezi dva obory. Snaží se dělat obojí, jak nejlépe umí.