
TL;DR: bude popis Keycloaku, systému pro řízení přístupu s otevřeným zdrojovým kódem, analýza vnitřní struktury, detaily nastavení.
Úvod a hlavní myšlenky
V tomto článku se podíváme na hlavní myšlenky, které je třeba mít na paměti při nasazení clustera Keycloak na Kubernetes.
Pokud se chcete dozvědět více o Keycloaku – navštivte odkazy na konci článku. Pro hlubší praktické porozumění můžete prozkoumat s modulem, který implementuje hlavní myšlenky tohoto článku (návod k nasazení naleznete tamtéž, v tomto článku bude přehled vnitřní struktury a nastavení, pozn. překladatele).
Keycloak je komplexní systém napsaný v Javě a postavený na aplikačním serveru . Zjednodušeně řečeno, je to framework pro autorizaci, který poskytuje uživatelům aplikací federaci a možnost SSO (single sign-on).
Zveme vás k přečtení oficiálního nebo pro podrobné porozumění.
Spuštění Keycloaku
Pro spuštění Keycloaku jsou potřeba dva trvale uložené datové zdroje:
- Databáze, která se používá pro uchovávání stabilních dat, například informací o uživatelích
- Datagrid cache, která slouží k vyrovnávání dat z databáze a také pro uchovávání některých krátkodobých a často měnících se metadat, například uživatelských relací. Realizuje se , který je obvykle výrazně rychlejší než databáze. V každém případě jsou data uchovávaná v Infinispan efemérní – a není nutné je nikam ukládat při restartu clustera.
Keycloak funguje ve čtyřech různých režimech:
- Běžný — jeden a pouze jeden proces, konfiguruje se pomocí souboru standalone.xml
- Běžný cluster (variant vysoké dostupnosti) – všechny procesy musí používat stejnou konfiguraci, kterou je třeba synchronizovat ručně. Nastavení se uchovává v souboru standalone-ha.xml, navíc je nutné zřídit sdílený přístup k databázi a vyvažovač zátěže.
- Doménový cluster — nasazení clusteru v běžném režimu se rychle stává rutinním a nudným úkolem při růstu clustera, protože pokaždé, když se změní konfigurace, je nutné provést všechny změny na každém uzlu clustera. Doménový režim to řeší tím, že nastavuje nějaké společné úložiště a publikaci konfigurace. Tato nastavení se uchovávají v souboru domain.xml
- Replikace mezi datovými centry — pokud chcete spustit Keycloak v clusteru s několika datovými centry, často na geograficky různých místech. V této variantě každý datové centrum bude mít svůj vlastní cluster serverů Keycloak.
V tomto článku se podrobně podíváme na druhou variantu, tedy běžný cluster, a také se dotkneme tématu replikace mezi datovými centry, protože tyto dvě varianty dává smysl spouštět v Kubernetes. Naštěstí v Kubernetes není problém se synchronizací nastavení několika podů (uzlů Keycloak), takže doménový cluster nebude příliš složitá na vytvoření.
Také prosím věnujte pozornost tomu, že slovo cluster až do konce článku bude používáno výhradně ve vztahu k skupině uzlů Keycloak, které pracují společně, není nutné odkazovat na cluster Kubernetes.
Běžný cluster Keycloak
Pro spuštění Keycloak v tomto režimu je potřeba:
- nastavit externí sdílenou databázi
- nainstalovat vyvažovač zátěže
- mít interní síť podporující IP multicast
Nastavení externí databáze nebudeme rozebírat, jelikož to není cílem tohoto článku. Předpokládejme, že někde je fungující databáze — a máme k ní připojovací bod. Tyto údaje jednoduše přidáme do proměnných prostředí.
Pro lepší pochopení toho, jak Keycloak funguje v odolném (HA) clusteru, je důležité vědět, jak silně toto vše závisí na schopnostech Wildfly clusterování.
Wildfly používá několik subsystémů, některé z nich slouží jako vyvažovač zátěže, jiné pro zajištění odolnosti. Vyvažovač zátěže zajišťuje dostupnost aplikace při přetížení uzlu clusteru, zatímco odolnost zajišťuje dostupnost aplikace i v případě selhání části uzlů clusteru. Některé z těchto subsystémů:
mod_cluster: funguje společně s Apache jako vyvažovač HTTP, závisí na TCP multicastu pro vyhledávání uzlů ve výchozím nastavení. Může být nahrazen externím vyvažovačem zátěže.infinispan: distribuovaná cache, používající kanály JGroups jako transportní vrstvu. Dále může používat protokol HotRod pro komunikaci s externím clusterem Infinispan pro synchronizaci obsahu cache.jgroups: poskytuje podporu skupinové komunikace pro vysoce dostupné služby založené na kanálech JGroups. Pojmenované kanály umožňují instancím aplikace v klastru spojit se do skupin tak, aby komunikace měla vlastnosti, jako je spolehlivost, pořadí a odolnost vůči selháním.
Vyrovnávač zátěže
Při nasazení vyrovnávače jako ingress kontroléru v klastru Kubernetes je důležité mít na paměti následující věci:
Funkce Keycloaku předpokládá, že vzdálená adresa klienta připojujícího se přes HTTP k autentizačnímu serveru je skutečná IP adresa klientského počítače. Nastavení vyrovnávače a ingress by měly správně nastavovat HTTP hlavičky X-Forwarded-For a X-Forwarded-Proto, a také uchovávat původní hlavičku HOST. Poslední verze ingress-nginx (> 0.22.0)
Aktivace příznaku proxy-address-forwarding prostřednictvím nastavení proměnné prostředí PROXY_ADDRESS_FORWARDING v true dává Keycloaku informaci, že pracuje za proxy.
Také je třeba povolit sticky sessions v ingress. Keycloak používá distribuovanou cache Infinispan k uchovávání dat souvisejících se současnou autentizační a uživatelskou relací. Cache pracují s jedním vlastníkem ve výchozím nastavení, jinými slovy, tato konkrétní relace je uchovávána na určitém uzlu v klastru, zatímco ostatní uzly ji musí vzdáleně vyžádat, pokud potřebují přístup k této relaci.
Konkrétně nám v rozporu s dokumentací nefungovalo připojení relace s názvem cookie
AUTH_SESSION_ID. Keycloak způsoboval nekonečné přesměrování, proto doporučujeme zvolit jiné jméno cookie pro sticky session.
Také Keycloak připojuje jméno uzlu, který první odpověděl, k AUTH_SESSION_ID, a protože každý uzel ve vysoce dostupné variantě používá stejnou databázi, každý z nich samostatný a jedinečný identifikátor uzlu pro správu transakcí. Doporučuje se nastavit v JAVA_OPTS parametrech jboss.node.name a jboss.tx.node.id jedinečné pro každý uzel — může se například použít název podu. Pokud budete používat název podu — nezapomeňte na omezení 23 znaků pro proměnné jboss, takže je lepší použít StatefulSet místo Deployment.
Další problém je, že pokud se server odstraní nebo restartuje, jeho cache se ztrácí. S tímto ohledem by číslo vlastníků cache pro všechny cache mělo být nastaveno na alespoň dva, tak zůstane kopie cache. Řešení - spustit při spuštění podu, uložený do adresáře /opt/jboss/startup-scripts v kontejneru:
Obsah skriptu
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Nastavení CACHE_OWNERS na "${env.CACHE_OWNERS}" ve všech cache-kontejnerech
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=owners, value=${env.CACHE_OWNERS:1})
run-batch
stop-embedded-serverpo čemž nastavte hodnotu proměnné prostředí CACHE_OWNERS na požadovanou.
Privátní síť s podporou IP multicast
Pokud používáte Weavenet jako CNI, multicast bude fungovat hned — a vaše uzly Keycloak se uvidí, jakmile budou spuštěny.
Pokud nemáte podporu IP multicast v Kubernetes clusteru, můžete nastavit JGroups, aby pracoval s jinými protokoly pro vyhledávání uzlů.
První možnost — použití KUBE_DNS, který používá headless service pro hledání uzlů Keycloak, jednoduše předáte JGroups název služby, který bude použit pro vyhledávání uzlů.
Další možnost — použití metody KUBE_PING, která pracuje s API pro vyhledávání uzlů (je třeba nastavit serviceAccount s oprávněními list a získat, poté nastavte pody pro práci s tímto serviceAccount).
Způsob vyhledávání uzlů pro JGroups se nastavuje nastavením proměnných prostředí JGROUPS_DISCOVERY_PROTOCOL a JGROUPS_DISCOVERY_PROPERTIES. Pro KUBE_PING je třeba vybrat pody zadáním namespace a labels.
️ Pokud používáte multicast a spouštíte dva nebo více clusterů Keycloak v jednom Kubernetes clusteru (např. jeden v namespace
production, druhý —staging) — uzly jednoho clusteru Keycloak se mohou připojit k jinému clusteru. Určitě používejte jedinečnou multicast adresu pro každý cluster nastavením proměnnýchjboss.default.multicast.addressajboss.modcluster.multicast.addressvJAVA_OPTS.
Replikace mezi datovými centry

Konektivita
Keycloak využívá více samostatných clusterů cache Infinispan pro každé datové centrum, kde se nacházejí clustery Keycloak složené z uzlů Keycloak. Mezi uzly Keycloak v různých datových centrech však neexistuje žádný rozdíl.
Uzlů Keycloak využívají externí Java Data Grid (servery Infinispan) pro komunikaci mezi datovými centry. Komunikace probíhá prostřednictvím protokolu .
Cache Infinispan musí být nakonfigurovány s atributem remoteStore, aby bylo možné ukládat data na vzdálených (v jiném datovém centru, pozn. překladatele) cache. Existují samostatné clustery Infinispan mezi servery JDG, takže data uložená na JDG1 na lokalitě site1 budou replikována na JDG2 na lokalitě site2.
A nakonec, přijímající server JDG upozorňuje servery Keycloak svého clusteru prostřednictvím klientských spojení, což je rys protokolu HotRod. Uzly Keycloak na site2 aktualizují své cache Infinispan, a konkrétní uživatelská relace se stává také dostupnou na uzlech Keycloak na site2.
Pro některé cache je také možné neprovádět zálohování a zcela se vzdát zápisu dat prostřednictvím serveru Infinispan. K tomu je potřeba odstranit nastavení remote-store pro konkrétní cache Infinispan (v souboru standalone-ha.xml), po čemž některá konkrétní replicated-cache také přestane být potřeba na straně serveru Infinispan.
Nastavení cache
Existují dva typy cache v Keycloak:
Lokální. Nachází se blízko databáze, slouží ke snížení zatížení databáze a také ke snížení latence odezvy. V tomto typu cache jsou uloženy realm, klienti, role a uživatelská metadata. Tento typ cache se nereplikuje, i když je tento cache součástí clusteru Keycloak. Pokud dojde ke změně v nějakém záznamu v cache, ostatním serverům v clusteru je zaslána zpráva o změně, po čemž je záznam vyloučen z cache. Viz popis
workdále, pro podrobnější popis postupu.Replikovaný. Zpracovává uživatelské relace, offline tokeny a také sleduje chyby přihlášení pro identifikaci pokusů o phishing hesel a dalších útoků. Ukládaná data v těchto cache jsou dočasná, uchovávají se pouze v operační paměti, ale mohou být replikována v rámci clusteru.
Cache Infinispan
Relace — koncept v Keycloak, jednotlivé cache, které se nazývají authenticationSessions, slouží k uchovávání dat konkrétních uživatelů. Požadavky na tyto cache obvykle potřebují prohlížeče a servery Keycloak, ne aplikace. Zde se projevuje závislost na sticky sessions a tyto cache není potřeba replikovat, ani v režimu Active-Active.
Tokeny akcí. Další koncept, obvykle používaný pro různé scénáře, kdy uživatel musí něco udělat asynchronně prostřednictvím e-mailu. Například během procesu zapomenuté heslo cache actionTokens slouží k sledování metadat souvisejících s tokeny – například token byl již použit a nemůže být znovu aktivován. Tento typ cache by měl být obvykle replikován mezi datovými centry.
Kešování a stárnutí uložených dat pracuje na tom, aby snížil zátěž na databázi. Takové kešování zlepšuje výkon, ale přidává zřejmý problém. Pokud jeden server Keycloak aktualizuje data, ostatní servery musí být o tom informovány, aby mohly provést aktualizaci dat ve svých cache. Keycloak používá místní cache realmy, users a autorizace pro cache dat z databáze.
Také existuje samostatná cache work, která se replikována po všech datových centrech. Ta sama o sobě neuchovává žádná data z databáze, ale slouží k odesílání zpráv o stárnutí dat uzlům clusteru mezi datovými centry. Jinými slovy, jakmile se data aktualizují, uzel Keycloak pošle zprávu ostatním uzlům ve svém datovém centru a také uzlům jiných datových center. Po obdržení takové zprávy každý uzel provede vyčištění odpovídajících dat ve svých místních cache.
Uživatelské relace. Cache s názvy sessions, clientSessions, offlineSessions a offlineClientSessions, se obvykle replikují mezi datovými centry a slouží k uchovávání dat o uživatelských relacích, které jsou aktivní během aktivity uživatele v prohlížeči. Tyto cache pracují s aplikací, která zpracovává HTTP požadavky od koncových uživatelů, takže jsou spojeny se sticky sessions a měly by být replikovány mezi datovými centry.
Ochrana proti útokům hrubou silou. Cache loginFailures slouží k sledování údajů o chybách přihlášení, například kolikrát uživatel zadal nesprávné heslo. Replikace této mezipaměti je záležitostí správce. Pro přesné počítání je však dobré aktivovat replikaci mezi datovými centry. Na druhou stranu, pokud se tyto údaje nereplikují, může to zlepšit výkon, a pokud se tato otázka vyskytne — replikaci lze také neaktivovat.
Při nasazení clusteru Infinispan je nutné přidat definice mezipamětí do konfiguračního souboru:
Je nutné nakonfigurovat a spustit cluster Infinispan před spuštěním clusteru Keycloak.
Poté je třeba nakonfigurovat remoteStore pro mezipaměti Keycloak. K tomu stačí skript, který se vytváří obdobně jako předchozí, který se používá pro konfiguraci proměnné CACHE_OWNERS, je třeba ho uložit do souboru a umístit do adresáře. /opt/jboss/startup-scripts:
Obsah skriptu
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Aktualizace subsystému infinispan ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Přidání vzdáleného připojení k infinispan serveru **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
echo ** Aktualizace pracovní položky replicated-cache **
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=work,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/replicated-cache=work:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace prvku sessions v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=sessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace offlineSessions v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=offlineSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace clientSessions v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=clientSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=clientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace offlineClientSessions v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=offlineClientSessions,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=offlineClientSessions:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace loginFailures v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=loginFailures,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace actionTokens v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
cache=actionTokens,
remote-servers=["remote-cache"],
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=actionTokens:write-attribute(name=statistics-enabled,value=true)
echo ** Aktualizace authenticationSessions v distribuovaném cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Aktualizace subsystému undertow ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-serverNezapomeňte nainstalovat JAVA_OPTS pro uzly Keycloak pro funkci HotRod: remote.cache.host, remote.cache.port a jméno služby jboss.site.name.
Odkazy a další dokumentace
Článek byl přeložen a připraven pro Habr zaměstnanci — intenzivní kurzy, videokurzy a firemní školení od praktikujících specialistů (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Zdroj: habr.com
