Až po vás auditor poprvé bude chtít audit trail, nejspíš vyexportujete CSV z logovacího systému, který už stejně provozujete, a bude to vypadat v pořádku. Časová razítka, uživatelé, akce, ID zdrojů, všechno v pořadí. Pak někdo položí jedinou otázku, na které záleží: jak víme, že ten soubor odpovídá tomu, co se skutečně stalo? Immutable audit log, který nikdo mimo váš tým nedokáže nezávisle ověřit, není důkaz. Je to tvrzení o vašem vlastním chování, vyrobené systémy, které řídíte, ve formátu, který jste si zvolili. Auditoři jsou v tomhle zdvořilí. Regulátoři čím dál míň.
„Immutable“ znamená ve většině infrastrukturních stacků něco slabšího, než to slovo naznačuje. Obvykle to znamená append-only dohodou - aplikace jen vkládá a všichni se shodli, že UPDATE nikdo nespustí. Někdy to znamená retenční politiku na objektovém úložišti nebo WORM bucket, což chrání před smazáním, ale neříká vůbec nic o tom, co se zapsalo na začátku. Všechno to jsou reálné kontroly. Ani jedna z nich ale neumožní třetí straně dodatečně odhalit změnu. A přesně ta mezera - mezi tohle nikdo nemá měnit a dokážeme, že to nikdo nezměnil - je celé téma.
Jak funguje tamper-evident audit trail
Vezměte každé pole záznamu, na kterém záleží - akci, kdo ji provedl, za jakou organizaci jednal, kterého zdroje se týkala, změněné hodnoty, correlation ID, úroveň rizika, adresu klienta - serializujte je v pevně daném pořadí, přidejte hash předchozího záznamu a přežeňte to přes SHA-256. Výsledný otisk uložte na řádek jako entry_hash a otisk předchůdce jako prev_hash. První řádek žádného předchůdce nemá, takže jeho prev_hash je prázdný. Všechno za ním je přivařené k tomu, co bylo před ním.
Užitečný je až důsledek. Změňte jediný znak ve tři měsíce starém záznamu a jeho přepočítaný hash přestane sedět s tím, co je uložené na řádku. Smažte celý řádek a prev_hash toho následujícího ukazuje na předchůdce, který neexistuje. Vložte vymyšlený záznam a v posloupnosti pro něj není platné místo. Nemusíte věřit úložišti, operátorovi ani dodavateli. Potřebujete záznamy, pravidla hashování a skript. Tohle z toho dělá tamper-evident audit trail, a ne jen úhledný log.
Buďme přesní v tom, co to nedává. Hash chain je tamper-evident, ne tamper-proof. Útočník, který má zápis do databáze a zná pravidla hashování, může záznam přepsat a pak přepočítat všechny hashe za ním - výsledný řetěz se ověří bez chyby. Co tohle porazí, je publikování hlavy řetězu někam, kam útočník nedosáhne: podepsat aktuální entry_hash klíčem drženým mimo databázi, vyexportovat ho, poslat auditorovi, zapsat ho do jiného systému. Kterákoliv z těch věcí zmrazí historii k danému okamžiku.
Proč na jediném writeru záleží víc, než čekáte
Hash chain má právě jednu hlavu a každý nový záznam si ji musí přečíst, než se na ni naváže. Pusťte proti stejné tabulce dva writery a začnou závodit - oba přečtou stejnou hlavu, oba se na ni naváží a jeden z nich je od té chvíle špatně. Řetěz se ohlásí jako rozbitý a vy strávíte den hledáním útočníka, který neexistuje. Bereme to jako invariant, ne jako preferenci: audit frontu konzumuje a do tabulky zapisuje jeden proces, a zapnutí druhého writeru je vědomý cutover, ne škálovací páčka.
Selhání ověření musí být čitelná, protože operátor reaguje na první slovo, které si přečte. Náš verifikátor rozlišuje tři důvody. chain_broken znamená špatné navázání, tedy že prev_hash neodpovídá předchůdci. content_tampered znamená, že navázání sedí, ale přepočítaný hash z uložených polí vychází jinak. entry_never_hashed znamená, že řádek byl zapsán úplně bez hashe. Jen ten druhý je důkazem manipulace, a slití všech tří do jedné hlášky je způsob, jak si založit bezpečnostní incident kvůli chybě v testu.
Ten třetí případ není hypotetický a stojí za to ho přiznat na sebe. Několik řádků v našem vlastním řetězu zapsal test, který vkládal přímo do tabulky raw SQL a obešel writer, takže obě hashovací pole zůstala prázdná. Databázové triggery, které z tabulky dělají append-only, odmítají i DELETE, takže ty řádky jsou trvale neopravitelné. Zůstanou v řetězu navždy a každý běh ověření na ně dojede. Opravou byl helper, který testy musí používat, a strážný test, který spadne v okamžiku, kdy někdo zase sáhne po raw SQL.
Co má auditor dostat do ruky
Výstupem není screenshot dashboardu s nápisem „chain valid“. Je to CSV se samotnými záznamy včetně obou hashovacích sloupců - id, akce, actor, organizace, customer scope, typ a ID zdroje, úroveň rizika, correlation ID, adresa, user agent, prev_hash, entry_hash a časové razítko - plus písemná specifikace toho, která pole přesně do otisku vstupují a v jakém pořadí. S těmihle dvěma věcmi si auditor napíše dvacet řádků Pythonu a ověří vaši práci, aniž by od vás cokoliv dalšího potřeboval. To je důkaz: něco, co můžete předat a pak nad tím ztratit kontrolu.
Drobnost s reálnými následky: exportovaná auditní pole jsou řetězce, které ovlivňuje útočník, a tabulkové procesory spustí cokoliv, co začíná znakem rovná se, plus, minus nebo zavináčem. Auditní export, který otevře shell na auditorově notebooku, je nezapomenutelný způsob, jak neprojít auditem. Každé pole v našem exportu je escapované a hodnoty vypadající jako vzorec se neutralizují dřív, než se dostanou do souboru. Je to ten typ kontroly, který vypadá jako puntičkářství přesně do chvíle, než někdo otevře evidence pack v Excelu.
Pod tím vším musí ty nebezpečné operace odmítat samotná tabulka. Append-only vynucené v aplikačním kódu je slib, který vydrží přesně do chvíle, než někdo napíše migraci, čisticí skript nebo dobře míněnou opravu ve dvě ráno. Databázové triggery, které na auditní tabulce odmítnou UPDATE i DELETE, mění ten slib v omezení. Znamená to taky, že aplikace a úložiště se můžou rozejít jen jedním směrem: databáze může odmítnout zápis, který aplikace chtěla, ale aplikace nikdy nemůže tiše přepsat databázi.
Immutable audit log ověřujte pravidelně
Většina týmů, které mají verify endpoint, ho zavolá jednou - při demu. To je špatná kadence. Rozbitý řetěz objevený ve chvíli, kdy se ptá auditor, je forenzní problém pokrývající kolik měsíců zrovna uplynulo; tentýž rozbitý řetěz objevený plánovaným během je bug report s časovým razítkem. Ověřování nad velkou tabulkou na to musí být stavěné. To naše prochází záznamy po stránkách v pořadí, místo aby si všechno natáhlo do paměti, protože tabulka roste s každou mutací na platformě a verifikátor, který funguje jen na malých tabulkách, je verifikátor, který jednou přestane fungovat.
Druhým návykem je pokrytí. Je snadné dát audit logging do HTTP middlewaru, sledovat, jak se každá API mutace objevuje v trailu, a prohlásit to za hotové. Pak cron archivuje zákazníkova data, konzument fronty odvolá agenta, worker na pozadí rotuje credential - a nic z toho v trailu není, protože nic z toho nebyl HTTP request. Tyhle cesty musí logovat explicitně. Nepříjemná pravda je, že akce s nejmenší pravděpodobností zalogování jsou přesně ty, které vyšetřovatel chce vidět nejvíc.
NIS2 i DORA tlačí stejným směrem: ke kontrolám, které jde předvést, ne jen tvrdit. Za použití SHA-256 vám nikdo certifikát nedá a netvrdíme, že ano. Ale když se někdo zeptá, kdo třetího března změnil pravidlo na firewallu, je velký rozdíl mezi výsledkem dotazu a výsledkem dotazu, jehož integritu si může ověřit cizí člověk. Tři otázky, které stojí za to položit každému, kdo vám prodává immutable audit log: můžu si vyexportovat hashe, můžu si je přepočítat bez vašeho softwaru, a co se stane, když se řetěz ohlásí jako rozbitý?