Auditní modul
Cílem modulu je umožnit detailní sledování chování uživatele na webu v takové podobě, která umožní zpětně rekonstruovat jeho chování.
Konfigrace modulu
Minimální konfigurace s povoleným UI
<auditmodule> <gui namedDefault="edee"/></auditmodule>Auditování security kontrol
Úspěšné autorizační kontroly (Security.authorize) lze směrovat do auditního modulu místo do legacy
databázového audit logu (<databaseauditlogger/>). V security konfiguraci se uvede:
<security ...> <eventauditlogger/></security>Každá kontrola se uloží jako operace security.<právo> (např. security.change, security.publish),
moduleObjectType odpovídá názvu security modulu a do argumentů se ukládá jazyk, itemId a url,
pokud jsou k dispozici. Kontroly práva view se defaultně neauditují (probíhají při každém
zobrazení chráněných dat) — zapínají se atributem <eventauditlogger auditViewChecks="true"/>.
Pokud jsou nakonfigurovány oba loggery, má přednost eventauditlogger.
Prohlížení záznamů probíhá v prohlížečce auditního modulu, legacy čtecí API vrací prázdné výsledky.
Výmaz starých záznamů
Auditní log se z pochopitelných důvodů může plnit velmi rychle a z tohoto důvodu je zde implementována jednoduchá funkcionalita, která jednou denně spustí výmaz starých záznamů.
Ve výchozím nastavení se mažou záznamy starší než 1095 dní (3 roky), ale tuto hodnotu lze konfigurovat v modulu takto:
<auditmodule> <gui namedDefault="edee"/> <auditConfig> <deleteRecordsOlderThanDays>365</deleteRecordsOlderThanDays> </auditConfig></auditmodule>Pro spouštění funkcionality se využívá JobManager, pokud má tedy výmaz probíhat, musí být modul spuštěn.
Prohlížečka záznamů
Pro prohlížení a vyhledávání auditních záznamů byla vytvořena jednoduchá prohlížečka, která je integrována v Edee, v "Nastavení - Diagnostika - Auditing".
Odezva hledání
Vzhledem k množství dat, které mohou být v auditním logu uloženy, se může stát, že nevhodně položený dotaz zabere několik desítek vteřin. Zkušenost z projektu, kde jsou v implementaci auditního logu miliony záznamů, je taková, že typicky hledám v auditním logu, když nastane nějaký problém. Nejdříve vyfiltruji data z období, kdy nastal problém a pak dohledávám podrobnosti podle operationName.
Nicméně se může stát, že Váš useCase bude jiný a budete vědět, že budete vyhledávat podle nějakých údajů ve sloupci operationArgs. Pokud jste si takové skutečnosti vědomi už při nasazení auditního modulu, pak bych asi doporučil vytvořit si custom index manuálně (byť to není ideální postup).
Aktuálně jsou vytvořeny DB indexy na sloupcích:
- loginName
- objectId
- created
- operationName
- sessionId
Struktura ukládaných dat
Data jsou ukládána do tabulky T_EDEE_AUDIT_LOG (prefix EDEE je možná trochu matoucí, nicméně nezbytný kvůli možným kolizím na již existujících projektech), struktura záznamu je následující:
-
id- id záznamu -
loginName(povinné) - přihlašovací jméno uživatele, který provedl operaci -
maskedLoginName- přihlašovací jméno admin uživatele, který se "přepnul" na jiného uživatele. Tato funkcionalita je dostupná jak Edee, tak v Registration modulu.Pozor - nejde o maskování hodnoty. Navzdory názvu se zde nic neanonymizuje: neprázdná hodnota znamená "došlo k přepnutí identity" - v
loginNameje uživatel, za kterého se jednalo, vmaskedLoginNameje původní (přepínající) uživatel. Nikdy sem nezapisujte maskovanou/redigovanou kopiiloginName; prohlížečka i AI nástrojeaudit.*toto pole čtou jako původního uživatele, takže redigovaná kopie předstírá přepnutí identity u každého záznamu a skutečná přepnutí se stanou nerozlišitelnými. Skrývání osobních údajů (loginu, e-mailu) před tím, kdo audit čte, patří až do místa zobrazení - záznam se vykreslí se začerněnou hodnotou - ne do uloženého záznamu, který musí zůstat dohledatelný. -
edeeUser- obsahuje informaci o tom, zda uživatel byl přihlášen z Edeeho, nebo z Registration modulu (případně jiné frontend implementace přihlášení) -
sessionId- id relace (interakce), do které operace patří; slouží ke sdružení souvisejících záznamů. Záměrně není vázáno na konkrétní transport - může jít o id HTTP session, MCP session, konverzace AI agenta apod. U samostatně stojících operací (např. běh jobu) je prázdné. -
moduleName(povinné) - název modulu -
objectId- Id objektu (entity), se kterou auditovaná metoda pracuje. Např. když uživatele provede uložení záznamu, pak by zde mělo být Id tohoto záznamu. Pokud nejde jednoznačně stanovit, pak bude prázdné. -
operationName(povinné) - název auditované operace. Pokud auditováno pomocí anotace (viz dále), pak zde bude uložena signatura auditované metody včetně plného názvu třídy. Pokud je auditováno přímo v kódu, pak záleží na implementátorovi, jaký název použije. Je ale nutné, aby byl unikátní alespoň v rámci modulu, jinak nebude možné jednoznačně určit vznik záznamu. -
operationArgs- argumenty operace. Do tohoto pole je možné vložit informace o vstupních datech auditované operace, které má smysl z hlediska auditingu sledovat. Může zde být uloženo poměrně velké množství dat (DB datový typ je TEXT/CLOB), je ale třeba používat s rozvahou, protože pokud je auditovaná operace volaná často, pak množství dat v auditním logu také poroste velmi rychle. -
operationReturnData- do tohoto pole se mohou ukládat informace o hodnotách, které byly výsledkem auditované operace. I sem se dá uložit velké množství dat. -
operationResult- sem se ukládá informace o výsledku operace, tj. zda operace skončila úspěšně, či chybou. -
moduleObjectType- do tohoto pole lze uložit typ pro moduleObject integraci -
created- datum a čas vytvoření auditního záznamu
Integrace auditingu do kódu
Do kódu lze auditing implementovat dvěma způsoby - pomocí anotace/AOP nebo publikací Spring eventů.
Jiná cesta zápisu neexistuje. Obě varianty končí publikací události AuditEvent, kterou zpracuje
AuditEventListener - to je jediné místo, kde se do záznamu doplní vše, co ho činí dohledatelným: přihlášený
uživatel, původní uživatel při přepnutí identity (maskedLoginName), příznak edeeUser, fallback
_system_user_ a čas vzniku. Přímé volání AuditManager.storeAudit() z produkčního kódu tuto logiku tiše
obejde - AuditManager je určen pro čtení a správu záznamů (dotazy, výmaz), ne pro zápis auditovaných operací.
Pro integraci je nutné doplnit maven závislost na API:
<dependency> <groupId>com.fg.cps</groupId> <artifactId>lib_interface</artifactId> <version>9.4.0</version></dependency>V obou případech je pro funkci nutný spuštěný auditní modul (auditmodule - com.fg.edee.audit.cps.AuditModule).
Použití anotace @Audited
Tento způsob je určen k auditingu public metod springových bean, typicky to budou metody na úrovni business tříd (managerů).
Pro AOP je nutné ještě doplnit závislost:
<dependency> <groupId>com.fg.edee</groupId> <artifactId>lib_audit</artifactId> <version>1.0.0</version></dependency>Integrace spočívá ve dvou krocích:
- Ve Spring konfiguraci modulu je nutné importovat konfigurační třídu
com.fg.edee.audit.aop.AuditAopConfiguration - Vložení
@Auditedanotace k metodám, které mají být auditovány (musí se jednat opublicmetody, vnitřní volání metod auditováno není)
Stručný popis Auditingu přes AOP
Přilinkováním souboru AuditAopConfiguration se zajistí, že se pomocí AOP vyhledají všechny metody s @Audited anotací
a obalí se jejich volání pomocí Around pointcutu, což umožňuje provádět operace před i po vykonání metody. Zpracování
požadavku je následující:
- Před voláním metody se načtou informace o argumentech metody (je nutné provést před voláním metody, protože se můžou v metodě změnit) a načtou se také další "statické" informace jako název modulu a název metody.
- Po volání metody se načtou informace o vrácených datech, výsledku operace (false, pokud operace vyhodila exception) a trochu překvapivě se zde také načítá Id objektu ze vstupních dat (u nově vytvářených záznamů Id objektu vznikne až uvnitř metody)
- Když jsou všechny data načtena, pak se vypublikuje událost (viz dále), kterou zachytí Auditní modul. Ten do dat navíc vloží login přihlášeného uživatele a aktuální datum a provede uložení do DB.
- Publikace eventy je synchronní, ale ukládání probíhá v samostatné transakci.
- Failsafe - proces načítání dat pro audit i proces publikace eventy je vždy obalen pomocí try - catch bloku, takže případná chyba se pouze zaloguje, ale neovlivní to běh auditovaného kódu.
Možnosti anotace @Audited
Ukázka konfigurace:
@Audited(objectId = "#pojo.id", args = "#pojo.name", returnData = "returnObject.id", moduleObjectType = "testingPojo", operationNameFormat = OperationNameFormat.SIMPLE)public TestingPojo findMatchingPojo(TestingPojo pojo) { ...}Možnosti jednotlivých nastavení:
objectId- zde je možné pomocí SpELu specifikovat hodnotu pro objectId.args- opět se pomocí SpELu specifikuje hodnota, která bude uložena do argumentů auditu. Pokud je potřeba uložit více argumentů, tak je třeba je explicitně vyjmenovat a ve SpELu z nich poskládat řetězec. Pokud je cílovým argumentem object, pak se nad objektem zavolá toString() a uloží se jeho výstup.
Pozor: pokud nejsou args nakonfigurovány, vezmou se všechny argumenty metody, zavolá se nad nimi toString() a uloží se ve formě pseudo JSONu, aby bylo možné výstup jednotlivých argumentů odlišit. Pokud by tedy nebyly argumenty ve výše uvedeném příkladu konfigurovány, uložené argumenty by mohly vypadat takto:
{"pojo":"TestingPojo(id=null, name=Some name, age=20, currentDate=null)"}returnData- zde je možné specifikovat návratovou hodnotu, opět pomocí SpELu. Pozor, používá se Springová konvence, kdy vrácený objekt je referencován pomocí proměnné returnObject bez znaku # na začátku.moduleObjectType- uvedená hodnota bude v nezměněné podobě uložena do příslušného pole v auditním záznamu.operationNameFormat- umožňuje definovat formát názvu operace. K dispozici jsou tyto možnosti, pokud není zadáno, pak je použita varianta FULL_WITH_ARGS:GENERIC- obsahuje maximum informací o auditované metoděFULL- obsahuje název třídy včetně classpath a název metody bez atributůFULL_WITH_ARGS- obsahuje název třídy včetně classpath a název metody včetně atributů metodySIMPLE- obsahuje název třídy bez classpath a název metody bez atributůSIMPLE_WITH_ARGS- obsahuje název třídy bez classpath a název metody včetně atributů, názvy atributů jsou také bez classpath.
operationName- název operace, který bude v zadané podobě uložen do auditního logu. Pokud zadáte vlastní název, musíte si být jisti, že je unikátní, aby nebylo možné zaměnit jednu operaci za jinou.
Všechny nastavení jsou nepovinné a v auditním záznamu budou tyto hodnoty prázdné. Jedinou výjimkou jsou argumenty, jak bylo popsáno výše.
Použití anotace @AuditedArg pro konfiguraci auditovaných argumentů
Auditované argumenty lze alternativně konfigurovat pomocí anotace @AuditedArg, kdy lze jednoduše specifikovat argumenty metody a jejich případné atributy, které se mají uložit do auditu. Anotací je možné dle potřeby použít více, výsledné údaje jsou ukládány ve formě JSONu, pokud je cílová proměnná objekt, pak se nad ní opět volá toString(). Ukázka konfigurace:
@Audited(objectId = "#pojo.id", returnData = "returnObject")@AuditedArg(argName = "pojo", properties = "age, name")@AuditedArg(argName = "language")public String convertPojoToString(TestingPojo pojo, String language) { ...}Uložené argumenty by v tomto případě vypadaly např. takto:
{ "pojo":{ "name":"Jimmy", "age":"45" }, "language":"cs"}Pokud je použita anotace @AuditedArg, pak má přednost před nastavením args v anotaci @Audited.
Integrace pomocí události
Tento způsob integrace umožňuje programátorovi mít pod kontrolou, jaká data se budou auditovat a kdy.
Typicky se toto hodí v případě, kdy je potřeba uložit auditní informaci v místě, kdy není prostě možné anotaci použít (například - auditing v rámci RamJet Interceptoru, nebo v průběhu hromadně prováděné akce, apod.)
Je možné naimplementovat auditing nad kódem třetí strany, pokud se jedn o Spring beany. V tomto případě je
nutné vytvořit vlastní Aspect, idálně jednoduše přetížit a s drobnými úpravami použit implementaci
com.fg.edee.audit.aop.AuditingAspect.
Implementace
Je třeba vytvořit instanci třídy com.fg.edee.audit.model.AuditImpl, pomocí builder metodu vložit požadovaná data a
předat publikovat událost com.fg.edee.audit.event.AuditEvent. Tu si zpracuje auditní modul.
Informace o přihlášeném uživateli - atributy loginName, maskedLoginName a edeeUser se přidají automaticky při zpracování události.
Pokud je v atributu loginName uvedená hodnota, tak pak se už v rámci zpracování události žádné vyhodnocení neprovádí. Tj. pokud je do dat zadán loginName, pak je nutné nastavit také správně atribut edeeUser, případně maskedLoginName, pokud je aktivní přepnutí identity.
Volitelně lze vyplnit sessionId - id relace/interakce, do které operace patří (HTTP session, MCP session, konverzace agenta). Slouží k tomu, aby šlo v prohlížečce vyfiltrovat všechny záznamy jedné interakce.
Načtení přihlášeného uživatele
Informace o přihlášeném uživateli se do auditních dat ukládají při zpracování události v auditním modulu. Přihlášený uživatel se načítá pomocí statických metod. Postup načtení informace o přihlášeném uživateli je následující:
- Nejprve se načte Edee uživatel. Pokud je přihlášený a zároveň požadavek byl směrován na administační UI, pak se použije tento uživatel.
- Pokud nebyl Edee uživatel nalezen, zkusí se získat přihlášený uživatel z Registration modulu.
- Pokud není dostupný, pak se zkusí získat uživatele ještě přes
com.fg.interaction.user.CommonUserProvider. Tato možnost je tu hlavně z důvodu, kdy by bylo implementováno nějaké custom přihlášení. - A pokud nebyl nalezen žádný uživatel, pak se jako loginName použije řetězec system_user. To může být legální stav, např. pokud se audituje proces, který byl spuštěn přes JobManagera.
