20. prosince 2017

Spring Security, SAML & ADFS: Úvod

Posledních pár týdnů jsem se teď mordoval se Spring Security, abych v grandiózním finále doiteroval k autentikaci pomocí SAML vůči ADFS.

Mimochodem, přijde mi, že poslední dobou, je to se Springem, jako s jakoukoliv jinou (velkou) dominantní značkou - pokud se pohybujete uvnitř dané domény, na vyhrazeném dvorečku, všechno frčí, jak na drátkách. Jakmile to zkusíte zintegrovat s něčím ne-nativním, začnou probémy. A rozlousknout ty problémy chvilku trvá - zvlášť, když StackOverflow mlčí a oficiální dokumentace se k (technologickým) cizincům moc nezná.

Ale to jsem odbočil, zpátky k tématu. Tentokrát se nebudu věnovat ani aktuálnímu use casu, ani těm problémům, ale zaměřím se jen na to, jak Springovský SAML s ADFS funguje a jak to v aplikaci naimplementovat a celkově nakonfigurovat.

Nicméně, pokud jste se ještě stále nechytli, o čem to mluvím a přemýšlíte, jestli číst dál, tak to bude o: lehce komplikované autentikaci webové aplikace oproti Active Directory (takový Microsoftí LDAP).

SAML slovníček

Neuškodí si trochu prosvištět názvosloví. I když SAML nepřináší nic moc převratného, přeci jenom říká určitým věcem svým vlastním způsobem.
  • SAML - Security Assertion Markup Language
  • User Agent (UA) - software, skrz který uživatel komunikuje se zabezpečeným zdrojem, typicky browser.
  • Service Provider (SP) - systém, který poskytuje zabezpečený zdroj a akceptuje authentication assertions a poskytuje Single Sign-On (viz následující body).
  • Identity Provider (IdP) - systém, který vydává authentication assertions a podporuje Single Sign-On.
  • SAML Assertions - assertions jsou gró SAML zpráv, které lítají mezi SP a IdP. Obsahují bezpečnostní informaci/e trojího typu: authentication statements, attribute statements a authorization decision statements. Nás budou zajímat první dva typy: jestli je uživatel autentikovaný a do jakých Active Directory skupin patří.
  • Single Sign-On (SSO) - přihlášení uživatele do více systémů jedinou akcí.
  • Single Logout (SLO) - odhlášení uživatele z více systémů jedinou akcí.
  •  SAML Metadata - XML data formát pro popis specifického SAML deploymentu, tedy SP nebo IdP.

ADFS slovníček

I ADFS má vlastní okruh termínů:
  • ADFS - Active Directory Federation Services
  • Claims - prohlášení týkající se uživatelů, které se primárně používá pro autorizaci přístupu k aplikacím. Claims jsou to, co potřebujeme dostat do SAML assertions jako attribute statements.
  • Claims Provider - systém, který poskytuje claims.
  • Relying Party Trust - partnerský systém, který zpracovává claims a kterému ADFS důvěřuje.
  • Federation Metadata - XML data formát pro výměnu konfigurace mezi Claims Providerem a Relying Party Trust.

SAML Binding

Následující popis SSO a SLO je poměrně detailní (musím se pochválit, že podrobnější jsem nevygoogloval) a je zaměřený na jednu konkrétní technologickou integraci - Spring Security SAML a ADFS. To znamená, že pro jiné konstelace to může fungovat "trošku" jinak.

Rovněž stojí za zmínku, že SAML specifikace definuje tzv. binding, tj. jak jsou SAML requesty a responsy mapovány na standardní message a komunikační protokoly. Pro SSO v browseru se většinou společně používají HTTP Redirect Binding a HTTP POST Binding. V následujících diagramech byste je měli bez problémů identifikovat. Co je podstatné - u tohoto druhu bindingu teče veškerá komunikace přes User Agent (browser), takže tam např. není žádná komunikace na pozadí mezi SP a IdP.

SAML Single Sign-On

Na následujícím obrázku jsem si dal hodně záležet a věřím, že jeho vysvětlující a informační hodnota je enormní, podaná krystalicky čistým způsobem. Přesto si neodpustím kratičké shrnutí:
  1. Browser (UA) přistoupí na chráněný zdroj na Service Providerovi (SP), který přesměruje UA na svoje login URL.
  2. SP vygeneruje SAML request a přesměruje UA na Identity Providera (IdP), kterému je předán SAML request.
  3. IdP vrátí UA přihlašovací formulář (jméno, heslo).
  4. UA pošle IdP credentials uživatele, spolu se SAML requestem.
  5. IdP předá credentials Claims Providerovi (CP), který ověří uživatele a v případě úspěchu vrátí před-konfigurované claims.
  6. IdP přebalí claims do assertions, které obalí do SAML response a tu pak vloží do HTML formuláře, který pošle UA. Součástí formuláře je i JavaScript, který může formulář automaticky odeslat.
  7. UA automaticky odešle formulář se SAML response na SP. V případě, že je v UA vypnutý JavaScript, musí uživatel odeslat formulář klepnutím na tlačítko.
  8. SP ověří přijaté SAML assertions a pokud je všechno v pořádku, přesměruje UA na původně vyžádaný zdroj.

SAML Single Sign-On

Uvedené schéma zobrazuje komunikaci pro ne-autentikovaného uživatele. V případě, že je uživatel již přihlášen - ať už lokálně na daném SP, nebo díky SSO na jakémkoliv jiném SP (který je registrován u daného IdP) - tak se přeskočí posílání a odesílání ADFS login formuláře (a samozřejmě komunikace s CP) a UA rovnou obdrží SAML response.

IdP Discovery

Uvedený scénář se dá zpestřit ještě o jednu věc. Vztah mezi SP a IdP je m:n
  • jeden IdP může obsluhovat několik SP a stejně tak
  • jeden SP si může vybrat z několika IdP.
V prvním případě se nic zvláštního neděje, právě od toho tu je SSO. V druhém případě je to trochu komplikovanější - jak si UA/SP vybere, vůči kterému IdP se autentikovat?

Tenhle případ řeší IdP Discovery. Místo úvodního přesměrování na login URL aktuálního SP dojde k přesměrování na stránku se seznamem všech zaregistrovaných IdP, z nichž si uživatel explicitně vybere.

Nastavit IdP Discovery pomocí Spring Security SAML není nijak složité, nicméně pro tento a následující články s touto možností nepracuji.

SAML Single Logout

Když jsem se pustil do kreslení předešlého diagramu pro SSO, tak se mi výsledek tak zalíbil, že jsem si střihnul ještě jeden obrázek - pro Single Logout (SLO).

A aby to nebylo triviální, tak jsem si rozchodil dva SP, abych mohl zdokumentovat, jak probíhá odhlášení ze všech zaregistrovaných SP. Protože o tom SLO je: když se odhlásím na jednom SP, tak mě to automaticky odhlásí i ze všech ostatních SP.

SAML Single Logout

To be continued...

Abych udržel článek v rozumné čitelnosti, rozhodl jsem se téma rozdělit do krátkého mini-seriálku. Příště bych se podíval, jak vyměnit metadata mezi SP a IdP, plus jak nakonfigurovat ADFS.

V závěrečném díle bych probral konfiguraci Spring Security SAML. Můžete se těšit na popis Java configuration (ofiko dokumentace jede furt na XML) a samozřejmě to pofrčí na aktuálním Spring 5.

Související články


4. prosince 2017

Nešvary logování

Co se týká softwarového vývoje, logování je jedna z nejvíce zanedbávaných oblastí. Samozřejmě, pokud nejde o něco naprosto amatérského, tak je logování v každé aplikaci. Stejně tak, aby člověk pohledal vývojáře, který si během programování nevypisuje na konzoli potřebné runtime informace.

A bohužel, tak jako každý Javista má v CV napsaný Maven, i když umí jen naimportovat projekt do IDE a z příkazové řádky spustit mvn clean install; tak všichni o sto šest logují: chaoticky, nekonzistentně, bez vize, bez přemýšlení. A občas jsou ty logy dost odpudivé smetiště.

Tenhle pocit se ve mně hromadí léta letoucí. A jelikož jsem teď musel refaktorovat pár špatně designovaných a zanedbaných aplikací, rozhodl jsem se k tomu sepsat pár postřehů.

Obecný vývojářský přístup

To, jak se tým chová k logování se dá obvykle shrnout do tří axiomů:
  • Vývojáři si ad hoc logují to, co momentálně potřebují během vývoje aktuální feature. Co se jednou vloží do zdrojáků, to už tam na věky zůstane.
  • Operations/Support si stěžují, že v produkčním logu nejsou relevantní informace, zato spoustu balastu.
  • Kromě kavárenského stěžování si, s tím nikdo nic nedělá.

Situaci může drobně zlepšit, pokud má zákazník, nebo produktový tým definovanou nějakou explicitní logging policy. Daleko častější ale je, že politika logování je buď vágní, nebo žádná. Dá se to popsat i tak, že chybí vize a kontrola, jak logování provádět a používat.

Za dané situace, jsou největšími "viníky" vývojáři, protože jsou to právě oni, kdo do kódu logovací zprávy zanášejí, stejně tak jako je na jejich libovůli jakou severitu zprávám nastaví. Záleží na kontextu, ale někdy/často(?) existuje dokonce "logovací super-padouch"... technical leader, který tuto oblast buď zanedbává, nebo rovnou ignoruje. Je to nejspíš on, kdo by měl způsob logování definovat a kontrolovat.

Ponechme však stranou analýzu "kdo za to může" a pojďme se podívat, jak se špatné logování projevuje.

Špatný mindset

Již jsem naznačil, že za špatný stav logování můžou většinou vývojáři. To není nic překvapivého - jedním ze základních znaků echt vývojáře je, že trpí chronickým tunelovým viděním. U logování se to projevuje tak, že programátoři nepřemýšlí, co se s aplikací stane, jakmile opustí jejich vývojové prostředí.

Ačkoliv aplikace poběží roky na produkci, tak většina logování reflektuje relativně krátkou vývojovou fázi.

Chybějící konvence a postupy

Tohle je obecný problém, který se vyskytne kdykoli mají lidé volnost v zápisu textu zprávy. Kromě logování jsou to typicky commit komentáře. Číst historii je pak (masochistický) požitek, kdy člověk, jako archeolog, prozkoumává hranice lidské kreativity.

"Všechno je relativní", tak proč ne úrovně logování? Každý vývojář dokáže sám, subjektivně a správně posoudit, jaká má být pro danou zprávu severita logování. Trace/Debug/Info, vždyť se to na produkci odfiltruje, tak co?

Co takhle zmapovat flow nějaké entity, jak prochází procesem a systémem? Někdy jo, někdy ne, někdy tohle ID, jindy tamto. Stejně je to všechno "Request ID", bez ohledu na počet requestů, vrstev a systémů. Anebo radši "Session ID", co na tom, že je to bezstavový? "Korelační" je sprosté a zakázané slovo. A vůbec, aby to někdo pochopil, musí na tom pár týdnů/měsíců dělat, takže je jedno, jak se to bude jmenovat.

Nesmyslné logování v testech

Když spustíte testy, viděli byste na konzoli radši takovýhle výpis?
:clean
:compileJava
:processResources
:classes
:compileTestJava
:processTestResources
:testClasses
:test

BUILD SUCCESSFUL in 42s
6 actionable tasks: 6 executed

Nebo byste raději viděli 20 obrazovek balastu, který lítá z logů Hibernate a Spring testů? Předešlý výpis tam samozřejmě bude geniálně ukrytý jak hrst jehel v kupce sena.

Přitom pomoc je triviální - vypněte pro testy logování:


Výpisy na konzoli

Kdo by si čas od času nezahřešil starým dobrým:
System.out.println("Bla bla");
Výpisy na konzoli do logu nepatří. Ani když je to v testech. Samozřejmě, záleží na nastavení vašeho logovacího frameworku - někdy jsou výpisy na sdtout přešměrovaný do logu, někdy ne. Používat by se ale neměly (téměř) nikdy.

Jedinou výjimkou by mohlo být, pokud píšete CLI aplikaci. Ale i tam bych zvážil, jestli místo println nepoužívat logování s vhodným formátem a severitou zprávy. Minimálně to půjde vypnout v testech.

Dedikovaný logger z interního frameworku

Zažili jste někdy, že by si firma vyvíjela vlastní framework? Já už jsem si tím párkrát prošel. Možná jsem jen neměl štěstí... ale pokaždé to byl průser - tam kde vám to 1/3 práce ušetřilo, tam vám to později 2/3 práce přidalo. K internímu frameworku samozřejmě patří custom logger. Jinak by to nebylo ono.

Ona je to vlastně dobrá myšlenka - některé z výše zmíněných problémů by se daly takovým speciálním loggerem podchytit. Bohužel, v realitě to bylo stejně úspešný jak implementace Scrumu v kovaným enterprise-korporátním prostředí. No, možná jsem jen neměl štěstí.

Zapomeňte na custom/interní/proprietární loggery! Vykašlete se na to a použijte vanilla logování svého srdce.

Jak z toho ven?

Tak jako na dovolený poznáte na hned první pohled rozdíl mezi rozvinutou a rozvojovou zemí, tak u funkčních aplikací poznáte, jestli někdo přemýšlel i kousek za kompilaci kódů. Pročetli jste si někdy logovací zprávy když vám startuje čistý aplikační server, nebo rozumný buildovací nástroj (ne, Maven to není)?

Zkuste někdy taky psát takové pěkné logy. Chce to jenom:
  • Mít vizi, jak má logování vypadat.
  • Dělat code review.
  • Čas od času udělat na logování audit.

Jaký je váš oblíbený logovací nešvar?