Automatické odemykání šifrovaných disků: Tang, Clevis a Gentoo
Full system encryption s pomocí LUKS už funguje delší dobu a není problém ji rozchodit. Zajímavější část nastává ve chvíli, kdy chceme disky odemykat trochu jinak, než jenom zadáváním hesla z klávesnice. Takže si vyzkoušíme Network Bound Disc Encryption, tedy stav, kdy se počítač automaticky odemkne pomocí klíče získaného ze sítě (který by měl dostat jen ve chvíli, kdy je počítač spuštěn v bezpečné síti). Zní to možná pitomě, nicméně pokud encryption server běží někde schovaný a je přístupný jen z lokální sítě (třeba přes VPN někam), při ukradení počítače/serveru má obvykle daná osoba smůlu, protože ke klíči se již nedostane. Takže jediná chvíle, kdy se lze ke klíči dostat, je v prostředí, kde stanice zrovna běží. Ano, v tuhle chvíli (narozdíl od TPM, u kterého se sází na to, že ten klíč prostě nikomu jinému nevydá) se lze ke klíči teoreticky dostat. Pokud má někdo dost času si s tím v té lokalitě hrát... u TPM na druhou stranu systém nabootuje vždy, takže i po odnesení je dost prostoru hledat bugy ve všem, co na dané stanici naběhne...
Kromě zmíněného LUKSu jsou klíčové nástroje tang a clevis. Tang je server, který přes síť na vyžádání posílá svůj klíč. Clevis je zase nástroj běžící naopak na zašifrované stanici, který se stará o odemykání svazků. Clevis toho umí více, používá se například při odemykání svazků pomocí TPM chipu ve stanici. Nicméně mimo jiné umí klíč získat i z Tang serveru a na to se teď zaměříme. O celém tomhle systému před časem vyšel článek na "rootu":https://www.root.cz/clanky/sifrovane-disky-na-serveru-automaticke-odemyk.... Protože celý systém není až tak komplikovaný a nepřinesl bych nic nového, zaměříme se hlavně na to, jak to rozchodit na Gentoo distribuci (doporučuji tedy přečíst i původní článek). Konkrétně na Gentoo s OpenRC (tedy bez systemd), kde nastává celá řada komplikací...
Tang
Tang je bezestavový server, který je vlastně poměrně jednoduchý. Jeho bezestavovost má výhodu a nevýhodu. Nevýhoda je, že při kompromitaci (ukradení) nějaké stanice nelze její klíč nějak revokovat. Je vlastně potřeba vygenerovat nový klíč na tangu, čímž ale přestanou fungovat klíče i na všech ostatních stanicích. Výhoda zase je, že tam v podstatě není ani co nastavovat. Prostě se to spustí na nějakém TCP portu a ono to běží. V Gentoo je Tang (i Clevis) v guru overlay repository. Takže si podle "instrukcí":https://wiki.gentoo.org/wiki/Project:GURU/Information_for_End_Users zapneme danou repository a emergneme balíček tang (je maskovaný ~ keywordem). Tedy v případě, že i ten chceme mít někde na gentoo (serveru) - není to podmínkou...
Takže v /etc/conf.d/tangd si nastavíme listen adresu a port a službu spustíme. To je vše.
Clevis
Na stanici, kterou chceme odemykat, nainstalujeme clevis ze stejné overlaye jako tang. Zkontrolujeme, že máme clevis minimálně verze 19-r1, protože právě od téhle verze v něm jsou patche, díky kterým fungují dracut moduly bez systemd. Samotné nabindování clevisu je jednoduché, pustíme příkaz clevis luks bind -d /dev/sdXY tang '{"url":"IP:PORT"}', kde sdXY zaměníme za náš block device, na kterém sedí šifrovaný fs a IP vyměníme za IP adresu tang serveru, PORT za port, na kterém běží. Po spuštění bude potřeba zadat heslo k šifrovanému svazku a potvrdit klíč tang serveru a to je vše.
Zajímavější část začíná až nyní - donutit to nabootovat. Potřebujeme totiž, aby v initrd při bootu bylo něco, co se připojí k síti, zkontaktuje tang server a odemkne zařízení. Genkernel toto neumí, takže na generování initramdisku použijeme dracut, který tedy nainstalujeme. Do /etc/dracut.conf jsem doplnil řádek:
- add_dracutmodules+=" network clevis "
který říká, že se mají přidat moduly network a clevis. Pozor na mezery okolo uvozovek, jsou třeba. Taky konfigurační soubor říká, že by se neměl tenhle soubor editovat, ale radši vyrobit separátní konfigurák v /etc/dracut.conf.d/*.conf - kdo to chce mít lepší, může to zkusit :-)
A nyní nastává velká zábava s laděním boot parametrů do cmdliny kernelu. Já si je doplnil přímo v grubu, tedy v /etc/default/grub do proměnné GRUB_CMDLINE_LINUX (kde následně je potřeba přegenerovat konfigurák grubu); alternativně by to mělo jít napsat i do dracut.conf před vygenerováním initrd. Postupně jsem přidával tyto parametry:
- rd.neednet=1 - nutnost, bez tohoto se vůbec neicinalizuje síťové připojení a bez něj se k síťovému serveru připojuje velice blbě
- ip= - a tady nastává celká zábava, na které jsem se na dlouhou dobu zasekl. Tohle je standardní kernel parametr na nastavení sítě, který se třeba používá i při bootu z NFS a podobně. Pokud máte v síti DHCP, pak by parametr ip=dhcp měl fungovat. Jenže... dracut okolo toho dělá velké tanečky. Jednak je otázka, kolik lidí s dracutem to ještě používá, protože dracut na systému se systemd tu síť umí nastavovat i network-managerem a dalšíma nějakýma takovýma věcma. Takže ten modul network-legacy dost možná moc lidí nepoužívá... a věc druhá, dracut i s network-legacy má různé scripty, co to vylepšují, pro případ, že chcete třeba statickou routu nebo tak. Bohužel v mém případě ip=auto vedlo k tomu, že PC si při bootu koretkně vzalo konfiguraci z DHCP, ale bohužel mu velice pravděpodobně jaksi zmizela default routa. Alespoň chování bylo takové, že na L2 síti počítač pingal, ale clevis házel chybu o nedosažitelnosti tang serveru a na routeru po komunikaci ani památky. A taky se to dost blbě debugguje, protože (viz debugging dále) jsem nenašel breakpoint, který by byl mezi nastavením sítě a spuštěním clevisu. Prostě buď dostanu shell před nastavením sítě a nevím co je tam blbě (klasický dhclient zafunguje dobře) nebo už clevis běží v nekonečné smyčce s chybou a shell nedostanu. Nakonec jsem to vzdal a vzhledem k velikosti sítě do parametru dal statickou konfiguraci, která funguje dobře. Takže pokud do parametru dáte 192.168.1.100::192.168.1.1:255.255.255.0:moje-pc:eth0:off - znamená to, že si PC nastaví IP adresu 192.168.1.100 s maskou 255.255.255.0, výchozí bránou 192.168.1.1, hostnamem moje-pc a bez další autokonfigurace na síťové kartě eth0. Pro úplnost dodávám, že můj tang server byl specifikovaný napřímo IP adresou. Pokud bych ho hledal DNS názvem, ještě bych raději zkontroloval, že v tom ramdisku je nějaký resolv.conf :-)
TRIM a discards
Háček číslo 3. Pokud náš systém je na SSD. LUKS defaultně nenastavuje discards flag na šifrovaných oddílech, což znamená, že TRIM na zadaných zařízeních nefunguje. Je to z důvodu bezpečnosti, jelikož u správně zašifrovaného disku nelze poznat, kde nějaká data jsou a kde ne (ikdyž se zašifruje úplně prázdný disk, na kterém jsou jen nuly, výsledkem bude náhodně vypadající data). TRIMem naopak zase operační systém říká SSD disku kde on žádná data nemá. Z důvodu toho, že SSD disky mají nějakou danou životnost paměťových buněk na počet přepisů. A jelikož většina systémů typicky pořád zapisuje na stejná místa, tak by velice rychle diskům některé buňky odešly, zatímco jiné by byly nepoužity. Z toho důvodu SSD disky zápisy rozkládají (takže ikdyž OS si myslí, že zapsal data na adresu X, disk je zapíše ve skutečnosti na Y a jen si pamatuje, že až si OS řekne o X, tak mu vrátí Y), aby se buňky opotřebovaly rovnoměrně. Jenže zase pak SSD disk potřebuje vědět, které buňky jsou prázdné, protože to on zase nemá jak zjistit, protože to ví OS... a OS mu to řekne trimem. V případě šifrování tedy TRIM prozradí, která místa disku jsou prázdná a tedy kde leží ta skutečná data... ale zase výrazně prodlouží život SSD. Můžete si vybrat :-)
Nicméně, pokud to chcete zapnout, tak je tu ten háček... V případě dracutu šlo používat volby cmdline kernelu jako rd.luks.options=discards a podobně. Ale když je zkusíte v tomto případě, nebude to fungovat. (Funkčnost ověřujeme příkazem cryptsetup status root_zařízení, ve výpisu by se mělo objevit flags: discards, pokud je TRIM funkční). Proč? Protože ty parametry výše fungují v případě, že vám to LUKS zařízení odemyká přímo nějaký dracutí modul. Který se typicky zeptá na heslo a tak dále. Jenže vy ty LUKS zařízení neodemykáte dracutem. Dracut pouze pustí clevis a všechno odemykání řídí clevis. Takže, jak donutit clevis, aby nastavil discards flag? Clevis na to má otevřený ticket na githubu... několik let :-) Takže to neumí...
Ale naštěstí máme workaround. Nabootujeme si naše PC jako normálně, cryptsetup status vrací výpis bez flags, takže to nefunguje. Příkazem cryptsetup --allow-discards --persistent refresh /dev/sdXY (kde sdXY je naše blokové zařízení se šifrovaným filesystémem) discards zapneme. A klíčový parametr --persistent říká, že by to takto mělo být nastaveno vždy. Restartujeme náš systém a znovu zkusíme cryptsetup status - nyní by již vše mělo být v pořádku.