Bezpečnostný kľúč namiesto hesla pri štarte: cesta k FIDO2 LUKS unlocku (a osem pokusov, kým to skutočne fungovalo)
Súčasť série o čerstvej migrácii pracovného laptopu na Pop!_OS s LUKS+Btrfs a hardvérovým FIDO2 kľúčom — o tejto jednej časti si to zaslúži vlastný priestor, lebo "enrolluj kľúč a hotovo" bola len prvá desatina práce.
Zadanie, ktoré znie jednoducho
Šifrovaný disk (LUKS), heslo pri každom boote. Cieľ: namiesto písania hesla len priložiť hardvérový bezpečnostný kľúč a dotknúť sa ho. FIDO2 podpora v cryptsetup na to existuje, libfido2 je dávno nainštalovaná, enrollment kľúča do LUKS slotu prebehne bez problémov.
A napriek tomu — po reštarte prišlo len obyčajné heslo. Žiadny FIDO2 pokus, žiadna chyba, nič.
Prvá vrstva: aký typ initrd vôbec máš
Skutočná príčina bola hlbšia, než chýbajúci balík. Tento konkrétny systém bootuje cez initramfs-tools (busybox-based, nie systemd-based initrd) a jeho cryptroot skript volá cryptsetup open s explicitným --key-file. Pri explicitnom key-file sa LUKS2 externé tokeny (FIDO2 je jeden z nich) vôbec neskúšajú — bez ohľadu na to, čo je v initrd zabalené.
Riešenie nebolo v úprave stock súborov, ale vo vlastnom skripte, ktorý sa spustí pred cryptroot-om (initramfs-tools garantuje poradie cez prereqs()), skúsi systemd-cryptsetup attach ... fido2-device=auto sám, a keď uspeje, cryptroot uvidí mapovanie už hotové a heslo vôbec nepýta. Keď zlyhá, ticho spadne na pôvodné heslo — fallback sa nikdy nemal rozbiť.
Vyzeralo to hotovo. Nasadené, initrd rebuild, teoreticky pripravené. Nasledovalo osem reštart-testov a každý odhalil inú vrstvu problému.
Test 1: kľúč nebliká, lebo nevie kedy
Prvý reálny reštart: opäť len heslo. Ukázalo sa, že skript pred pokusom o unlock nič nevypisoval — bezpečnostný kľúč len ticho zabliká LED, bez textovej výzvy. Bez signálu, kedy presne sa má kľúča dotknúť, pokus pravdepodobne ticho vypršal (timeout) a spadol na heslo presne podľa návrhu fallbacku.
Fix: skript vypíše "touch your security key now..." pred pokusom. Nasadené, overené priamo v zabalenom initrd.
Test 2 a 3: neviditeľný text za grafickým logom
Ďalší reštart — opäť nič, dokonca ani nová hláška nebola vidno. Vysvetlenie: kernel cmdline mal quiet splash, takže Plymouth preberá konzolu vlastným grafickým framebufferom. Obyčajný echo na /dev/console je fyzicky zapísaný, ale vizuálne prekrytý splash logom — jednoducho to nebolo vidieť, hoci sa to dialo.
Prvý pokus opravy (plymouth display-message) narazil na to, že aktuálna verzia/theme takýto príkaz vôbec nemá medzi platnými. Namiesto ladenia presného Plymouth API padlo rozhodnutie na univerzálnejší mechanizmus: plymouth hide-splash pred pokusom (zhodí grafiku na holú textovú konzolu) a show-splash po ňom — nezávislý od konkrétnej témy či verzie.
Test 4: falošné víťazstvo zo starého pokusu
Tu prišiel najzradnejší moment celej sagy. Boot vyzeral úspešne — PIN prompt sa zobrazil, kľúč sa dotkol, systém sa odomkol bez hesla. Zapísané ako vyriešené.
Bol to omyl. Ďalší "klasický" reštart o pár minút neskôr znova skočil rovno na heslo. Spätná diagnostika cez journalctl --list-boots odhalila, že ten úspešný boot bežal na úplne inom initrd (initrd-dracut-fido2.img) — samostatný, dávnejší a nikde poriadne nezapísaný experiment s dracutom namiesto initramfs-tools, ktorý som si sám kedysi rozbehol a potom naň zabudol. Fungoval preto, lebo dracut beží s plným systemd ako PID1, ktorý FIDO2 PIN prompt (cez systemd ask-password) obsluhuje automaticky. Bol to jednorazový výber v boot menu, nie skutočná oprava — ďalší reštart sa vrátil na pôvodný, stále rozbitý initramfs-tools variant.
Poučenie zapísané rovno na mieste: priebežné zapisovanie vlastného postupu nie je formalita — bez neho jeden rozbehnutý pokus nevie o druhom, a úspech na nesprávnej vetve sa ľahko vydá za vyriešený problém.
Testy 5-6: závod s časom a rozhodnutie zrušiť splash natrvalo
Ďalšia hypotéza: možno je zlyhanie príliš rýchle na zbadanie okom. Pridaný vynútený sleep 1 okolo pokusu. Kľúč fyzicky overený zapojený. Reštart — znova úplne nič.
To vylúčilo aj teóriu "príliš rýchlo", aj teóriu "zlý initrd" (priamo overené lsinitramfs-om, že skript aj knižnice tam skutočne boli). Namiesto ďalšieho naháňania Plymouth verzií padlo rozhodnutie na tvrdší, ale spoľahlivejší krok: natrvalo odstrániť splash z kernel cmdline. Plymouth potom vôbec neprevezme konzolu, takže čokoľvek sa vypíše, bude vidno vždy — bez závislosti na tom, ktorá verzia/téma akurát podporuje aké API.
Test 7: konečne čitateľná chyba
Prvý reštart bez splash priniesol niečo, čo predtým chýbalo najviac — konkrétnu chybovú hlášku: Device UUID=... does not exist or access denied. crypt_init() failed: Block device required.
Skript beží zámerne pred cryptroot-om, teda aj pred tým, než udev stihne vytvoriť /dev/disk/by-uuid/<UUID> pre LUKS partíciu. Jednorazový udevadm settle negarantuje, že "add" event bol na rýchlom boote vôbec spracovaný. Fix: aktívny polling na existenciu by-uuid cesty (každých 100 ms, max 10 s) namiesto jedného settle-volania.
Test 8: tri bugy naraz, odhalené priamym sledovaním konzoly
S čitateľnou konzolou stačilo po každom reštarte pozorne sledovať obrazovku — a odhalili sa tri ďalšie, dovtedy neviditeľné chyby naraz:
UUID=xxxje syntax pre/etc/crypttab, ktorú samotnásystemd-cryptsetupbinárka neprekladá — treba jej dať priamo/dev/disk/by-uuid/xxx.- Keyfile argument
-spúšťal interaktívny heslový prompt namiesto FIDO2 cesty — správna hodnota (overená priamo zosystemctl catunitu) je literálny reťazecnone. - Po oprave oboch bodov LUKS attach konečne štartoval správne, ale zamrzol bez PIN promptu a bez blikania kľúča. Príčina: FIDO2 PIN ide cez systemd
ask-passwordmechanizmus, ktorý potrebuje bežiaceho agenta na obsluhu na konzole. Dracut initrd (ten jediný "náhodne úspešný" test vyššie) má plný systemd ako PID1 a rieši to automaticky. Initramfs-tools beží na busybox inite — nič z toho tam samo od seba nie je, treba to doplniť ručne.
Fix: pridaný systemd-tty-ask-password-agent --watch na pozadí, spustený pred samotným attach volaním.
Konečné víťazstvo — a prečo bolo treba ho overiť nezávisle
Piaty reálny test s touto verziou konečne fungoval tak, ako mal od začiatku: PIN naslepo, dotyk kľúča, odomknutie bez hesla, rovno na login obrazovku. Po skúsenosti s falošným pozitívom z testu 4 prišlo tentoraz aj nezávislé overenie — journalctl --list-boots, uptime -s a /proc/cmdline potvrdili čerstvý boot na defaultnom systemd-boot zázname, žiadny jednorazový alternatívny výber. Nebol to náhodný úspech na inej vetve — bola to opakovateľná, bežná cesta.
Ako drobný dodatok ešte prišlo doladenie: PIN sa pri zadávaní zobrazoval čitateľne (kozmetický bezpečnostný nedostatok) — opravené ručným stty -echo/stty echo okolo behu ask-password agenta. A samostatne overený aj edge case bez zapojeného kľúča: PIN prompt sa zobrazí, po zlyhaní korektne spadne na normálne heslo, presne podľa návrhu.
Bonus kolo: prečo login obrazovka nežiadala dotyk, hoci sudo áno
Po tom, čo fungoval unlock disku pri boote, prišla ďalšia otázka — prečo login obrazovka (greeter) nežiada dotyk kľúča, keď to na starom systéme fungovalo. Porovnanie s pripojeným starým diskom (read-only) ukázalo rozdiel: na starom systéme bol pam_u2f.so riadok v /etc/pam.d/common-auth, ktorý includujú všetky PAM služby vrátane login greetera. Na novom systéme bol ten istý riadok pridaný len do samostatného sudo súboru — do common-auth sa pri migrácii nedostal, lebo je to root-owned zmena mimo bežného $HOME presunu.
Namiesto úpravy globálneho common-auth (čo by ovplyvnilo všetky PAM služby naraz) bol riadok pridaný scoped, priamo do konfigurácie login greetera, s korektným fallbackom na heslo pri zlyhaní.
Čo z toho zostáva ako poučenie
- "Enrolluj kľúč" je len prvý krok. Skutočná cesta k tomu, aby to fungovalo pri každom reštarte, závisí od typu initrd, boot splash nástroja a od toho, či beží plný systemd alebo len busybox init.
- Splash obrazovka dokáže skryť aj funkčný kód. Ak niečo "nefunguje" bezo stopy, over najprv, či to vôbec vidíš — nie, či to beží.
- Falošné pozitívum je horšie než zjavné zlyhanie. Overenie „na akom boot zázname presne bežal tento test" zachránilo pred nesprávnym uzavretím veci ešte raz.
- Priebežný zápis stavu nie je byrokracia. Práve to odhalilo, že "úspech" pochádzal z celkom inej, paralelnej vetvy práce.
- Priamy pohľad na konzolu je diagnostický nástroj, nie núdzové riešenie — keď nemáš log, to, čo vidno priamo na obrazovke, býva jediný dôkaz, ktorý máš.
Výsledok stál osem kôl testovania, ale dnes stačí priložiť kľúč a dotknúť sa ho — heslo ostáva len ako fallback, presne tak, ako to malo byť od prvého pokusu.
Riešite podobný problém?
Toto je presne typ práce, ktorú riešim aj pre iných — Linux servery, Docker nasadenia, zálohovanie a bezpečné nastavenie prístupu.
Pozrieť Linux & Docker služby