
TL;DR: kommer en beskrivning av Keycloak, ett system för Ätkomstkontroll med öppen kÀllkod, en analys av dess inre uppbyggnad och detaljer om konfiguration.
Introduktion och grundlÀggande idéer
I denna artikel kommer vi att se de grundlÀggande idéerna att tÀnka pÄ nÀr man distribuerar en Keycloak-kluster ovanpÄ Kubernetes.
Om du vill veta mer om Keycloak â vĂ€nligen se lĂ€nkarna i slutet av artikeln. För att dyka djupare in i praktiken kan du studera med modulen som implementerar de grundlĂ€ggande idĂ©erna i denna artikel (en guide till att starta finns dĂ€r ocksĂ„, i denna artikel kommer vi att ge en översikt av dess struktur och instĂ€llningar, översĂ€ttarens anm.).
Keycloak Àr ett omfattande system, skrivet i Java och byggt ovanpÄ applikationsservern . Kort sagt, det Àr ett ramverk för autentisering som ger applikationsanvÀndare federation och möjlighet till SSO (single sign-on).
Vi inbjuder dig att lÀsa den officiella eller för att fÄ en djupare förstÄelse.
Körning av Keycloak
För att köra Keycloak krÀvs tvÄ permanent lagrade datakÀllor:
- En databas som anvÀnds för att lagra bestÀndiga data, till exempel information om anvÀndare
- Datagrid-cache, som anvĂ€nds för att cacha data frĂ„n databasen, och för att lagra vissa kortlivade och ofta Ă€ndrade metadata, till exempel anvĂ€ndarsessioner. Implementeras av , som vanligtvis Ă€r betydligt snabbare Ă€n databasen. Men i alla fall Ă€r de data som lagras i Infinispan efemĂ€ra â och de behöver inte sparas nĂ„gonstans vid omstart av klustret.
Keycloak fungerar i fyra olika lÀgen:
- Standard â en och endast en process, konfigureras via filen standalone.xml
- Standardkluster (hög tillgĂ€nglighetsvariant) â alla processer mĂ„ste anvĂ€nda samma konfiguration, som mĂ„ste synkroniseras manuellt. InstĂ€llningarna lagras i filen standalone-ha.xml, det krĂ€vs dessutom att man gör databasen och belastningsbalanseraren gemensam.
- DomĂ€nkluster â att starta ett kluster i standardlĂ€ge blir snabbt rutinartat och trĂ„kigt nĂ€r klustret vĂ€xer, eftersom varje gĂ„ng konfigurationen Ă€ndras mĂ„ste alla Ă€ndringar genomföras pĂ„ varje nod i klustret. DomĂ€nlĂ€get löser detta genom att stĂ€lla in nĂ„got gemensamt lagringsutrymme och publicera konfigurationen. Dessa instĂ€llningar lagras i filen domain.xml
- Replikering mellan datacenter â om du vill köra Keycloak i en klusterkonfiguration med flera datacenter, oftast geografiskt spridda. I detta driftsĂ€tt kommer varje datacenter att ha sin egen kluster av Keycloak-servrar.
I den hÀr artikeln kommer vi att gÄ igenom det andra alternativet, det vill sÀga det vanliga klustret, och vi kommer Àven att beröra Àmnet kring replicering mellan datacenter, eftersom dessa tvÄ alternativ kan vara meningsfulla att köra i Kubernetes. Lyckligtvis finns det inget problem med att synkronisera instÀllningar mellan flera pods (Keycloak-noder) i Kubernetes, sÄ domenklustret blir inte sÀrskilt svÄrt att skapa.
Observera ocksÄ att ordet kluster fram till slutet av artikeln kommer att anvÀndas enbart i samband med en grupp av Keycloak-noder som arbetar tillsammans, utan att referera till Kubernetes-klustret.
Vanligt kluster av Keycloak
För att köra Keycloak i detta lÀge behövs:
- konfigurera en extern gemensam databas
- installera en lastbalanserare
- ha ett internt nÀtverk med stöd för IP-multicast
Vi kommer inte att gĂ„ in pĂ„ konfigurationen av den externa databasen, eftersom den inte Ă€r mĂ„let för denna artikel. LĂ„t oss anta att det finns en fungerande databas nĂ„gonstans â och att vi har en anslutningspunkt till den. Vi lĂ€gger helt enkelt till dessa uppgifter som miljövariabler.
För att bÀttre förstÄ hur Keycloak fungerar i en hög tillgÀnglighets (HA) kluster Àr det viktigt att veta hur mycket detta beror pÄ Wildflys klusteringsförmÄga.
Wildfly anvÀnder flera delsystem, vissa av dem anvÀnds som lastbalanserare, andra för hög tillgÀnglighet. Lastbalanseraren sÀkerstÀller applikationens tillgÀnglighet vid överbelastning av en nod i klustret, medan hög tillgÀnglighet garanterar applikationens tillgÄng Àven vid fel pÄ vissa noder i klustret. Vissa av dessa delsystem Àr:
mod_cluster: arbetar tillsammans med Apache som en HTTP-lastbalanserare, Àr beroende av TCP multicast för att hitta noder som standard. Kan ersÀttas av en extern lastbalanserare.infinispan: en distribuerad cache som anvÀnder JGroups-kanaler som transportlager. Kan dessutom anvÀnda HotRod-protokollet för kommunikation med ett externt Infinispan-kluster för att synkronisera cacheinnehÄllet.jgroups: tillhandahÄller gruppkommunikationsstöd för hög tillgÀnglighetstjÀnster baserade pÄ JGroups-kanaler. Namngivna kanaler lÄter applikationsinstanser i klustret ansluta till grupper sÄ att kommunikationen har egenskaper som tillförlitlighet, ordning och felkÀnslighet.
Lastbalanserare
NÀr lastbalanseraren installeras som ingress-controller i Kubernetes-klustret Àr det viktigt att tÀnka pÄ följande saker:
Keycloaks funktion förutsÀtter att den externa klientens adress, som ansluter via HTTP till autentiseringstjÀnsten, Àr den verkliga IP-adressen för klientdatan. InstÀllningarna för lastbalanseraren och ingress mÄste korrekt stÀlla in HTTP-huvuden X-Forwarded-For och X-Forwarded-Proto, samt bevara den ursprungliga rubriken HOST. Den senaste versionen ingress-nginx (> 0.22.0)
Aktivera flaggan proxy-address-forwarding genom att stÀlla in miljövariabeln PROXY_ADDRESS_FORWARDING pÄ true ger Keycloak förstÄelse för att den körs bakom en proxy.
Det Àr ocksÄ nödvÀndigt att aktivera sticky sessions i ingress. Keycloak anvÀnder Infinispan distribuerad cache för att lagra data relaterade till den aktuella autentisering sesssionen och anvÀndarsessionen. Cacher fungerar med en enda Àgare som standard, med andra ord sparas denna specifika session pÄ en nod i klustret, och andra noder mÄste begÀra den pÄ distans om de behöver Ätkomst till denna session.
Konkreta uppgifter har trots dokumentationen inte fungerat med namnet cookie för sessionen
AUTH_SESSION_ID. Keycloak orsakade en omdirigeringsloop, sÄ vi rekommenderar att vÀlja ett annat cookie-namn för sticky session.
Dessutom bifogar Keycloak namnet pÄ den nod som först svarade till AUTH_SESSION_ID, och eftersom varje nod i den hög tillgÀngliga versionen anvÀnder samma databas, mÄste varje nod en separat och unik nodidentifierare för att hantera transaktionerna. Det rekommenderas att sÀtta i JAVA_OPTS parametrar jboss.node.name och jboss.tx.node.id unika för varje nod - du kan till exempel sÀtta podnamnet. Om du anvÀnder podnamn, kom ihÄg att det finns en begrÀnsning pÄ 23 tecken för jboss-variabler, sÄ det Àr bÀttre att anvÀnda StatefulSet istÀllet för Deployment.
Ett annat problem Àr att om en server tas bort eller startas om, kommer dess cache att försvinna. Med det i Ätanke bör antalet cacheÀgare för alla cacher stÀllas in pÄ minst tvÄ, sÄ att en kopia av cachen kvarstÄr. Lösningen Àr att köra vid uppstarten av podden, genom att inkludera det i katalogen /opt/jboss/startup-scripts i containern:
InnehÄllet i skriptet
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * SĂ€tter CACHE_OWNERS till "${env.CACHE_OWNERS}" i alla cache-containrar
/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-serverdÀrefter stÀlla in vÀrdet för miljövariabeln CACHE_OWNERS till det som krÀvs.
Privat nÀtverk med stöd för IP multicast
Om du anvĂ€nder Weavenet som CNI kommer multicast att fungera direkt â och dina Keycloak-noder kommer att se varandra sĂ„ snart de startas.
Om du inte har stöd för IP multicast i Kubernetes-klustret kan du konfigurera JGroups för att fungera med andra protokoll för nodupptÀckten.
Det första alternativet Àr att anvÀnda KUBE_DNS, som anvÀnder headless service för att hitta Keycloak-noder, du anger helt enkelt JGroups namnet pÄ tjÀnsten som ska anvÀndas för att upptÀcka noder.
Ett annat alternativ Àr att tillÀmpa metoden KUBE_PING, som arbetar med API:er för att hitta noder (du mÄste konfigurera serviceAccount med behörigheter list och hÀmta, och sedan stÀlla in poddar för att arbeta med denna serviceAccount).
Metoden för att upptÀcka noder för JGroups konfigureras genom att stÀlla in miljövariablerna JGROUPS_DISCOVERY_PROTOCOL och JGROUPS_DISCOVERY_PROPERTIES. För KUBE_PING mÄste du vÀlja poddar genom att ange namnrymd och labels.
ïž Om du anvĂ€nder multicast och kör tvĂ„ eller fler Keycloak-kluster i ett och samma Kubernetes-kluster (lĂ„t oss sĂ€ga ett i namespace
production, den andra âstaging) â noderna i ett Keycloak-kluster kan gĂ„ med i ett annat kluster. Se till att anvĂ€nda en unik multicast-adress för varje kluster genom att stĂ€lla in variablerjboss.default.multicast.addressochjboss.modcluster.multicast.addresspĂ„JAVA_OPTS.
Replikering mellan datacenter

Anslutning
Keycloak anvÀnder flera separata Infinispan-cachekluster för varje datacenter dÀr Keycloak-kluster som bestÄr av Keycloak-noder Àr belÀgna. Men det finns ingen skillnad mellan Keycloak-noder i olika datacenter.
Keycloak-noder anvÀnder en extern Java Data Grid (Infinispan-servrar) för kommunikation mellan datacentren. Kommunikationen fungerar via protokollet .
Infinispan-cachemÄste konfigureras med attributet remoteStore, sÄ att data kan lagras i externa (i ett annat datacenter, översÀttarens anm.) cacher. Det finns separata infinispan-kluster bland JDG-servrar sÄ att data som sparas pÄ JDG1 pÄ platsen site1 kommer att replikeras pÄ JDG2 pÄ platsen site2.
Och slutligen meddelar den mottagande JDG-servern Keycloak-servrarna i sitt kluster via klientanslutningar, vilket Àr en funktion hos HotRod-protokollet. Keycloak-noderna pÄ site2 uppdaterar sina Infinispan-cacher, och en specifik anvÀndarsession blir ocksÄ tillgÀnglig pÄ Keycloak-noderna pÄ site2.
För vissa cacher Àr det ocksÄ möjligt att inte göra sÀkerhetskopior och helt överge dataskrivning via Infinispan-servern. För detta mÄste instÀllningen remote-store tas bort frÄn den specifika Infinispan-cachen (i filen standalone-ha.xml), varefter en specifik replicated-cache inte lÀngre kommer att vara nödvÀndig pÄ Infinispan-serverns sida.
Cachekonfigurationer
Det finns tvÄ typer av cachar i Keycloak:
Lokal. Den Àr belÀgen nÀra databasen och minskar belastningen pÄ databasen samt sÀnker svarstiden. I denna typ av cache lagras realm, klienter, roller och anvÀndarmetadata. Denna typ av cache replikeras inte, Àven om denna cache Àr en del av Keycloak-klustret. Om en post Àndras i cache meddelas övriga servrar i klustret om Àndringen, varefter posten tas bort frÄn cachen. Se beskrivningen
worknedan för en mer detaljerad beskrivning av proceduren.Replikabel. Den hanterar anvÀndarsessioner, offline-token och övervakar inloggningsfel för att identifiera försök till phishing av lösenord och andra attacker. De lagrade datan i dessa cacher Àr temporÀra, lagras bara i arbetsminnet, men kan replikeras över klustret.
Infinispan-cacher
Sessioner â ett koncept i Keycloak, separata cacher som kallas authenticationSessions, tillĂ€mpas för lagring av specifika anvĂ€ndardata. FörfrĂ„gningar frĂ„n dessa cachar behövs vanligtvis av webblĂ€saren och Keycloak-servrarna, inte av applikationerna. HĂ€r blir beroendet av sticky sessions tydligt, och sĂ„dana cachar behöver inte replikeras, inte ens i fall av Active-Active-lĂ€ge.
Ă
tgĂ€rdstokens. En ytterligare koncept som vanligtvis anvĂ€nds för olika scenarier dĂ€r, till exempel, en anvĂ€ndare mĂ„ste göra nĂ„got asynkront via e-post. Till exempel, under processen glöm bort lösenord cache actionTokens anvĂ€nds för att spĂ„ra metadata relaterad till tokens â till exempel en token som redan har anvĂ€nts och inte kan aktiveras igen. Denna typ av cache bör vanligtvis replikeras mellan datacenter.
Caching och förÄldring av lagrad data fungerar för att avlasta databasen. SÄdan caching förbÀttrar prestanda men ger en uppenbar utmaning. Om en Keycloak-server uppdaterar data mÄste andra servrar meddelas om detta sÄ att de kan uppdatera sina cachar. Keycloak anvÀnder lokala cachar realms, users och auktorisation för caching av data frÄn databasen.
Det finns ocksÄ en separat cache work, som replikerar över alla datacenter. Den lagrar inga data frÄn databasen, utan tjÀnar till att skicka meddelanden om förÄldrade data till klusternoder mellan datacentren. Med andra ord, sÄ snart data uppdateras skickar Keycloak-noden ett meddelande till de andra noderna i sitt datacenter och Àven till noder i andra datacenter. Efter att ha mottagit ett sÄdant meddelande rensar varje nod de relevanta data i sina lokala cachar.
AnvÀndarsessioner. Cachar med namnen sessions, clientSessions, offlineSessions och offlineClientSessions, replikerats vanligtvis mellan datacenter och anvÀnds för att lagra data om anvÀndarsessioner som Àr aktiva under anvÀndarens aktivitet i webblÀsaren. Dessa cachar arbetar med applikationen som hanterar HTTP-förfrÄgningar frÄn slutbrukare, sÄ de Àr kopplade till sticky sessions och bör replikeras mellan datacenter.
Skydd mot brute force-angrepp. Cache loginFailures tjĂ€nar för att spĂ„ra inloggningsfel, till exempel hur mĂ„nga gĂ„nger en anvĂ€ndare har skrivit in fel lösenord. Replikering av denna cache Ă€r Administrationens ansvar. Men för att fĂ„ en exakt rĂ€knare bör replikering aktiveras mellan datacenter. Ă
andra sidan, om dessa data inte replikeras, kan prestandan förbÀttras, och om frÄgan uppstÄr behöver replikeringen inte aktiveras.
Vid distribution av Infinispan-klustret mÄste cache-definitioner lÀggas till i konfigurationsfilen:
Det Àr nödvÀndigt att konfigurera och starta Infinispan-klustret innan Keycloak-klustret startas
DÀrefter mÄste remoteStore konfigureras för Keycloak-cacher. För detta rÀcker det med ett skript, som görs pÄ samma sÀtt som det föregÄende, som anvÀnds för att konfigurera variabeln CACHE_OWNERS, det ska sparas i en fil och placeras i katalogen /opt/jboss/startup-scripts:
InnehÄllet i skriptet
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Uppdatera infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** LÀgg till fjÀrrsockelbindning till infinispan-server **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})
echo ** Uppdatera elementet för replikerad-cache arbete **
/subsystem=infinispan/cache-container=keycloak/replikerad-cache=arbete/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=arbete,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/replikerad-cache=arbete:write-attribute(name=statistics-enabled,value=true)
echo ** Uppdatera distribuerad-cache sessionselement **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessioner/store=remote:add(
passivation=false,
fetch-state=false,
purge=false,
preload=false,
shared=true,
remote-servers=["remote-cache"],
cache=sessioner,
properties={
rawValues=true,
marshaller=org.keycloak.cluster.infinispan.KeycloakHotRodMarshallerFactory,
protocolVersion=${keycloak.connectionsInfinispan.hotrodProtocolVersion}
}
)
/subsystem=infinispan/cache-container=keycloak/distributed-cache=sessioner:write-attribute(name=statistics-enabled,value=true)
echo ** Uppdatera distribuerad-cache offlineSessions-element **
/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 ** Uppdatera distribuerad-cache clientSessions-element **
/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 ** Uppdatera distribuerad-cache offlineClientSessions-element **
/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 ** Uppdatera distribuerad-cache loginFailures-element **
/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 ** Uppdatera distribuerad-cache actionTokens-element **
/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 ** Uppdatera distribuerad-cache authenticationSessions-element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Uppdatera undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-serverKom ihÄg att stÀlla in JAVA_OPTS för Keycloak-noder för att anvÀnda HotRod: remote.cache.host, remote.cache.port och tjÀnstnamn jboss.site.name.
LĂ€nkar och ytterligare dokumentation
Artikeln har översatts och förberetts för Habr av medarbetare â intensivkurser, videokurser och företagsutbildning frĂ„n praktiker (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
KĂ€lla: habr.com
