Ukotvit jeden program k VPN v Linuxu je hlavně problém směrování, a pravidlo firewallu rozhoduje jen o tom, co se stane poté, co je směrování zvolené. Pokud v OUTPUT namatchujete UID programu dřív, než tunel vlastní výchozí trasu, pravidlo zahodí právě provoz, který jste chtěli ochránit. Stejné pravidlo také nevidí proces resolveru, který odpovídá na DNS dotazy programu. Dejte pořadí správně a jeden účet bez oprávnění se stane spolehlivým voličem rozděleného tunelu.

Dejte účtu trasu dřív než verdikt

Shoda vlastníka odpovídá na jednu otázku: sestavil paket lokální socket patřící tomuto UID? Nevybírá rozhraní a nemůže přesunout provoz, který hlavní tabulka už posílá přes fyzický NIC. Proto pravidlo UID bez odpovídající trasy program zablokuje, místo aby ho přesměrovalo.

Pošlete účet nejprve jeho vlastní tabulkou a potom nechte firewall potvrdit výsledek. Funguje to na hostiteli, jehož iproute2 rozumí uidrange, což ip rule help hlásí v jedné řádce. Tabulka 100 drží výchozí trasu tunelem a pravidlo posílá do této tabulky jen UID 1000. Všechno ostatní zůstává v hlavní tabulce a dál používá fyzické rozhraní.

ip rule add uidrange 1000-1000 table 100
ip route add default dev tun0 table 100

Druhý účet, který potřebuje jiný tunel, dostane tabulku 101 a vlastní pravidlo, protože dva profily nemohou oba vlastnit výchozí trasu ve stejné tabulce. Profily NetworkManager jsou systémové, takže tahle část zůstává ruční a pravidla zmizí při restartu, pokud je něco neobnoví. Napište tabulky do spouštěcího skriptu vedle definice tunelu, aby opětovné připojení nemohlo nechat jeden účet na špatné cestě.

Proč výjimka pro loopback tunel zabije

Většina zkopírovaných verzí pravidla zahazuje každý paket, který nevyjde přes loopback. Tunel není loopback, takže tato podoba zahazuje i provoz tunelu a výsledek vypadá jako mrtvý VPN, ne jako chyba firewallu.

Když OUTPUT běží, jádro už vybralo výstupní rozhraní, takže -o se porovnává s odpovědí trasy, ne se záhlavím paketu. Shoda vlastníka se konzultuje jen v OUTPUT a POSTROUTING a přesměrované pakety nenesou žádný socket, který by si přečetlo. Tahle kombinace znamená, že výjimka musí pojmenovat rozhraní tunelu a tuto shodu negovat, místo aby negovala loopback.

Loopback si pořád zaslouží vlastní ACCEPT nad DROP, protože první DNS paket z programu jde na místní stub přes lo. Dejte obě pravidla do tohoto pořadí a účet dorazí ke stubu, pak k tunelu a nikam jinam.

iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP

IPv6 je samostatná sada pravidel a dvojice jen pro v4 nechává ip6tables nedotčené. Program, který dává přednost záznamům AAAA, by pak vyšel fyzickým rozhraním, aniž by se kdy dostal k DROP. Zrcadlete obě pravidla v ip6tables, nebo na hostiteli vypněte IPv6, když tunel nenese žádnou v6 adresu. Ručně přidaná pravidla také zmizí při restartu, takže je uložte pomocí netfilter-persistent save nebo stejnou logiku znovu postavte jako tabulku nftables načítanou spouštěcí jednotkou.

DNS odpovědi přicházejí z jiného procesu

Dotaz programu na 127.0.0.53 projde ACCEPT pro loopback a právě tam shoda UID přestává. Stub resolver předává dotaz nahoru ze svého služebního účtu, takže pravidlo navázané na vpnapp nikdy neuvidí druhý dotaz. Pokud resolver pro spojení zvolí LAN server, odpovědi se vrátí přes fyzický NIC, zatímco vlastní pakety programu zůstanou v tunelu. Viditelný výsledek je funkční tunel s rozbitými jmény, nebo jména, na která odpovídá server, který jste nezvolili.

Opravte překlad jmen na spojení tunelu, ne v aplikaci. Dejte profilu tunelu DNS server a směrovací doménu a potom potvrďte pomocí resolvectl query a resolvectl status tun0 že resolver ho používá pro jména, na kterých záleží. Uvnitř síťového jmenného prostoru problém mizí, protože resolver prostoru zná jen tunel.

Ověřte pravidlo ze samotného účtu

Zeptejte se jádra, kterou trasu UID dostane, potom odešlete požadavek jako tento účet a porovnejte výstupní adresu s adresou svého účtu. Pokud obě hlásí stejnou adresu, pravidlo matchuje něco jiného než provoz, který jste testovali. První příkaz níže ukazuje hledání trasy bez odeslání paketu, což odděluje chybu směrování od chyby firewallu.

ip route get 1.1.1.1 uid 1000
sudo -u vpnapp curl -s https://ifconfig.co

Počítadla ukazují, zda pravidlo někdy vystřelilo. Spusťte iptables -vnL OUTPUT, přečtěte počet paketů vedle každého pravidla a nulový proti DROP po skutečném provozu berte jako chybu shody.

Předávaný provoz stojí mimo tento návrh. Kontejner nebo VM za mostem nemá místní socket, který by vlastnil vpnapp, takže shoda vlastníka nikdy nevystřelí a tyto pakety jdou podle politiky FORWARD. Pokud je nutné takový provoz pokrýt, otestujte ho zvlášť a napište politiku navázanou na most nebo adresu kontejneru. Návrh drží, když tabulka, výjimka rozhraní, resolver a pravidla v6 ukazují stejným směrem; vynechte kteroukoli z nich a účet buď prosakuje, nebo ztratí konektivitu.

Související články