Správa účtů v XML souborech a jejich distribuce
V tomto článku popíši, jak jsem pomocí IdM řešil správu účtů v XML souboru. Tento soubor je zdrojem účtů pro aplikaci běžící v tomcatu a je načítán pouze při startu. Po editaci souboru je proto potřeba tomcat restartovat. Aplikace běží na dvou serverech, před kterými je loadbalancer, jež mezi ně rozhazuje požadavky. XML soubory je tedy potřeba udržovat na obou dvou serverech. Navíc je požadováno, aby bylo možno ručně soubor modifikovat.
Požadavky by se daly shrnout následovně:
- Přidávat, editovat a odstraňovat záznamy v souboru.
- Změny musí být propagovány na oba dva servery.
- Aplikace musí být po aktualizaci souboru restartována.
- Vždy musí být dostupná alespoň jedna aplikace.
- Soubor může být ručně editován.
Řešení
Řešení je složeno ze dvou částí. První z nich představuje IdM, které bude spravovat jeden soubor. K tomu použije Shell Script Adapter, který bude volat perl skript pro editaci souboru. Další částí bude distribuční skript. Ten bude zajišťovat přenos souboru na všechny servery a restart aplikace.
Celkové řešení je znázorněno na obrázku.
Samotná editace XML souboru pomocí IdM není nijak těžká. Zajímavější to začíná být ve spojení s distribucí souboru a jeho možnou ruční editaci. V takovém případě se ze souboru stává zdroj sdílený mezi IdM, distribučním skriptem a administrátorem, který ho chce editovat. Aby bylo zaručeno, že každý z nich bude pracovat s aktuálními a konzistentními daty, tak je potřeba důsledně zamykat.
Pojďme se tedy nejdříve podívat na první část.
Editace souboru
XML soubor má velice jednoduchou strukturu. Z pohledu IdM nás zajímají pouze podelementy elementu „accounts“.
<authorization> <roles> <!-- Definice rolí. V tomto případě nezajímavá část. --> </roles> <accounts> <account name=“uziv1“ role=“role1“ /> <account name=“uziv1“ role=“role2“ /> <account name=“uziv2“ role=“role2“ /> <!-- Další účty --> </accounts> </authorization>
Pomocí elementů „account“ je definováno, kdo má přiřazenou jakou roli. Atribut „name“ obsahuje jméno uživatele a atribut „role“ obsahuje název přiřazené role. Pokud má uživatel více rolí, tak bude „mít“ i více elementů „account“.
U zákazníka je používán Oracle Waveset. Pro správu XML souboru se nejvíce hodí Shell Script Adapter, který je standardní součástí tohoto IdMka. Adaptér nedělá nic jiného, než že pro každou operaci volá příslušný skript.
Skript je spouštěn přes příkazovou řádku uživatele uvedeného v konfiguraci adaptéru. Pokud tedy budete mít problém s kódováním znaků, tak je potřeba správně nastavit proměnné prostředí uživatele z konfigurace. V našem případě to znamenalo doplnit do „~/.bashrc“ řádky:
export LC_ALL=cs_CZ.utf8 export LANG="$LC_ALL"
U Shell Script Adapteru je také třeba dát pozor na to, aby „Resource Schema“, tedy mapování atributů, obsahovalo na levé straně atribut s názvem „accountId“. V dokumentaci to sice napsané je, ale z vlastní zkušenosti vím, že to člověk lehce přehlédne. Trestem za tuto nepozornost je chybová hláška „An account lacking an accountId was ignored:“ během rekonciliace.
Většinu práce při editaci XML souboru má na starosti perl skript. Na práci s XML jsem použil knihovnu XML::Twig. Zde je část kódu, který do pole „roles“ načte všechny role uživatele se jménem v proměnné „accountId“.
my $twig = XML::Twig->new( twig_roots => { "/authorization/accounts/account[\@name=\"$accountId\"]" => sub { $userWasFound = 1; my $role = $_->att('role'); if ($role) { push @roles, $role; } } } ); $twig->parsefile(XML_FILE);
Všechny části kódu, které pracují se souborem musí být chráněny zámky. K tomu jsem použil funkci „flock“. Její použití může vypadat následovně:
open(LOCK_HANDLE, '>' . LOCK_FILE); flock(LOCK_HANDLE, LOCK_EX); # Kód, který má být chráněn zámky close(LOCK_HANDLE);
Výstup skriptu je ve formátu csv (pomocí knihovny Class::CSV). Důvodem je skutečnost, že výstup je třeba v adaptéru zpracovat a pro csv je již k dispozici hotový parser (collectCsvHeader a collectCsvLines).
Distribuce souboru
Je třeba zajistit, aby změny byly propagovány na všechny servery a aby aplikace běžící na těchto serverech tyto změny načetly.
IdM přímo spravuje pouze jeden soubor. Nic nebrání tomu, aby to byl přímo soubor na jednom ze serverů. Připadalo mi ale přehlednější upravovat IdMkem další soubor, který není čten žádnou aplikací a jednou za čas jim přepsat soubory, která aplikacemi využívány jsou. Tento další soubor je umístěn na stejném stroji jako IdM. Zde je také nástrojem „cron“ spouštěn jednou za pět minut distribuční skript, který:
- zamkne soubor sdíleným zámkem
- zkontroluje, že je XML soubor well-formed
- porovná hash aktuálního souboru s hashem, který byl spočítán posledně
- jestliže nedošlo ke změně hashe ukončí se
- uloží aktuální hash do pomocného souboru
- vytvoří kopii XML souboru, se kterou pak dále pracuje
- uvolní zámek na souboru
- pro každý server, kam se má soubor kopírovat:
- zkontroluje, že server běží
- přenese soubor
- provede restart serveru
- vyčká definovanou dobu
- zkontroluje, že server běží
- při jakékoliv chybě okamžitě skončí a pošle mail
Je vidět, že skript dělá docela dost práce. Projdeme nyní každý bod a trochu ho rozvedeme.
Nejdříve dojde k zamknutí sdíleným zámkem. IdM (a správně postupující administrátor) nyní mohou ze souboru pouze číst. Zápisy jsou blokovány. Následuje kontrola, že soubor je správně naformátovaný, abychom nešířili chybný soubor. Pomocí otisku souboru zjistíme, zda došlo ke změně.
my $sha1 = Digest::SHA1->new(); $sha1->addfile($file); return $sha1->hexdigest();
Pokud ke změně došlo, uložíme nový hash a vytvoříme kopii souboru. Kopii vytváříme proto, abychom mohli co nejdříve uvolnit zámek. Další běh skriptu může trvat minuty. Během toho se ale již používá kopii, kterou nikdo nemění. Originální soubor mezitím už může být modifikován.
V další fázi skript postupně přenáší soubor na všechny nakonfigurované servery. V rámci toho nejdříve zkontroluje, že běží aplikace na tomto serveru. Pokud by neběžela, tak něco není v pořádku a raději skončí. Kontrolu provádí tak, že stáhne login stránku aplikace a vyhledá na ni klíčové slovo.
my $content = get($webUrl); if ($content) { return ($content =~ /$webKeyword/); } else { return 0; }
Jestliže je vše v pořádku, přenese skript soubor a provede restart tomcatu. Restart se provádí přes ssh. Aby nedošlo po ukončení SSH spojení i k ukončení nastartovaného tomcatu, tak je využíván příkaz „nohup“. Po restartu je opět provedena kontrola běhu aplikace. Tentokráte ale kontrola probíhá v několika pokusech, mezi kterými je několik vteřin pauza. Jestliže i tento krok proběhne úspěšně, pokračuje skript s aktualizací dalšího serveru.
V případě neúspěchu je poslán mail administrátorovi.
my $mailer = new Net::SMTP::TLS( 'smtp.server.tld', Port => 25, Debug => 1, NoTLS => 1 ); my $fromAddr = 'fromaddr@company.tld'; my $toAddr = 'toaddr@company.tld'; $mailer->mail($fromAddr); $mailer->to($toAddr); $mailer->data; $mailer->datasend("From: $fromAddr\n"); $mailer->datasend("To: $toAddr\n"); $mailer->datasend("Subject: IdM - distribution of XML file failed\n"); $mailer->datasend("\n"); $mailer->datasend($mailText); $mailer->dataend; $mailer->quit;
Ruční editace
Při ruční editaci je nutné používat zámky stejným způsobem, jakým to dělají skripty. Pro editaci souboru „data.xml“ s využitím zámků lze použít příkaz:
flock -x lock -c "vi data.xml"
Soubor „lock“ je ten, který je zamykán. Skripty i administrátor musí používat stejný, jinak by zamykání nemělo smysl.
Závěr
I když si s sebou IdM nese kolem padesáti adaptérů určených k připojení konkrétních systémů, vyskytne se občas požadavek na připojení nějakého proprietárního, kdy nám ani takto velká množina adaptérů nepomůže. Jednou z možností je vytvoření vlastního adaptéru, což už bylo dříve na našem blogu popsáno. Často je ale vhodnější použít nějaký obecný adaptér a ten doupravit. V článku jsem se snažil ukázat možnosti Shell Script Adaptéru ve spojení s perl skripty a upozornit na některé technické komplikace, které se mohou vyskytnout. Pokud vás téma zaujalo, neváhejte mne kontaktovat na jan.gregor@bcvsolutions.eu.