
TL;DR: der vil være en beskrivelse af Keycloak, et system til adgangskontrol med åben kildekode, en gennemgang af den interne struktur, indstillingerne.
Introduktion og hovedideer
I denne artikel vil vi se på de vigtigste overvejelser, der skal huskes ved implementering af et Keycloak-kluster oven på Kubernetes.
Hvis du ønsker at vide mere om Keycloak, kan du finde links i slutningen af artiklen. For at dykke dybere ned i praktikken kan du undersøge med modulet, der implementerer hovedideerne i denne artikel (der findes en installationsvejledning der, og i denne artikel vil der være en oversigt over strukturen og indstillingerne, oversætterens bemærkning).
Keycloak er et omfattende system, skrevet i Java og bygget oven på applikationsserveren . Kort sagt er det et framework til autentificering, der giver applikationsbrugere forbundne muligheder og SSO (single sign-on).
Vi inviterer dig til at læse den officielle eller for en detaljeret forståelse.
Kørsel af Keycloak
For at Keycloak kan køre, er der behov for to konstant gemte datakilder:
- En database, der bruges til at opbevare faste data, som f.eks. oplysninger om brugere
- Datagrid cache, der anvendes til caching af data fra databasen samt til at opbevare visse kortlivede og ofte ændrede metadata som bruger-sessioner. Det implementeres , som normalt er betydeligt hurtigere end databasen. Men uanset hvad, er de data, der opbevares i Infinispan, efemere — og de skal ikke gemmes noget sted ved genstart af klustret.
Keycloak fungerer i fire forskellige tilstande:
- Normal — én og kun én proces, konfigureres via filen standalone.xml
- Normal klynge (højtilgængelig version) — alle processer skal bruge den samme konfiguration, som skal synkroniseres manuelt. Indstillingerne gemmes i filen standalone-ha.xml, derudover skal der laves fælles adgang til databasen og belastningsfordeleren.
- Domaine klynge — at køre et kluster i normal tilstand bliver hurtigt en rutinepræget og kedelig opgave, når klusteret vokser, da ændringer i konfigurationen skal indføres på hver enkelt node i klusteret. Domaine driftsmode løser dette problem ved at indstille et fælles lager og offentliggøre konfigurationen. Disse indstillinger gemmes i filen domain.xml
- Replikering mellem datacentre — hvis du ønsker at køre Keycloak i et cluster fra flere datacentre, ofte i forskellige geografiske placeringer. I dette arbejdsformat vil hvert datacenter have sit eget Keycloak-servercluster.
I denne artikel vil vi grundigt gennemgå den anden mulighed, nemlig et almindeligt cluster, og vi vil også kort berøre emnet om replikering mellem datacentre, da disse to muligheder giver mening at køre i Kubernetes. Heldigvis er der ikke et problem med synkronisering af indstillinger mellem flere pods (noder af Keycloak) i Kubernetes, så domæneclusteret vil ikke være særlig svært at oprette.
Bemærk også, at ordet cluster i hele artiklen kun vil blive anvendt med henvisning til en gruppe Keycloak-noder, der arbejder sammen; der er ikke behov for at henvise til Kubernetes-clusteret.
Et almindeligt Keycloak-cluster
For at køre Keycloak i denne tilstand skal du:
- konfigurere en ekstern fælles database
- installere en belastningsbalancer
- have et internt netværk, der understøtter ip multicast
Vi vil ikke gennemgå konfigurationen af den eksterne database, da dette ikke er målet for denne artikel. Lad os antage, at der et sted findes en fungerende database — og vi har en tilslutningspunkt til den. Vi vil blot tilføje disse oplysninger til miljøvariabler.
For bedre at forstå, hvordan Keycloak fungerer i et fejltolerant (HA) cluster, er det vigtigt at vide, hvor meget dette afhænger af Wildflys evner til clustering.
Wildfly anvender flere undersystemer, nogle af dem bruges som belastningsbalancer, nogle til fejltolerance. Belastningsbalancer sikrer tilgængelighed af applikationen under belastning af clusteret, mens fejltolerance garanterer tilgængelighed af applikationen, selv i tilfælde af fejl i dele af clusteret. Nogle af disse undersystemer er:
mod_cluster: arbejder sammen med Apache som en HTTP-belastningsbalancer, afhænger af TCP multicast til at finde noder som standard. Kan erstattes med en ekstern belastningsbalancer.infinispan: en distribueret cache, der bruger JGroups-kanaler som transportlag. Kan desuden anvende HotRod-protokollen til at kommunikere med en ekstern Infinispan-cluster for at synkronisere cacheindholdet.jgroups: giver support til gruppesamarbejde for højtilgængelige tjenester baseret på JGroups-kanaler. Navngivne kanaler gør det muligt for applikationsinstanser i klyngen at oprette forbindelse i grupper, så kommunikationen har egenskaber som pålidelighed, rækkefølge og fejlsensitivitet.
Belastningsbalancer
Når du installerer en belastningsbalancer som ingress-controller i en Kubernetes-klynge, er det vigtigt at tage følgende i betragtning:
Keycloaks funktion indebærer, at den eksterne adresse på klienten, der opretter forbindelse via HTTP til autentifikationsserveren, er den reelle IP-adresse for klientcomputeren. Indstillingerne for belastningsbalanceren og ingress skal korrekt indstille HTTP-overskrifterne X-Forwarded-For og X-Forwarded-Proto, samt bevare den oprindelige overskrift VÆRT. Den nyeste version ingress-nginx (> 0.22.0)
Aktivering af flaget proxy-address-forwarding ved at sætte miljøvariablen PROXY_ADDRESS_FORWARDING i true giver Keycloak forståelse for, at det kører bag en proxy.
Det er også nødvendigt at aktivere sticky sessions i ingress. Keycloak anvender Infinispan distribueret cache til at gemme data relateret til den aktuelle autentifikationssession og brugerens session. Cacherne fungerer med en ejer som standard, med andre ord gemmes denne specifikke session på en bestemt node i klyngen, og andre noder skal anmode om den eksternt, hvis de har brug for adgang til denne session.
Specifikt har ved os, modsat dokumentationen, ikke session vedhæftning med cookie-navnet
AUTH_SESSION_ID. Keycloak skabte en redirect-loop, så vi anbefaler at vælge et andet cookie-navn til sticky session.
Derudover vedhæfter Keycloak navnet på den node, der svarede først, til AUTH_SESSION_ID, og da hver node i den højt tilgængelige version bruger den samme database, skal hver af dem en separat og unik node-identifikator til håndtering af transaktioner. Det anbefales at angive i JAVA_OPTS parametre jboss.node.name og jboss.tx.node.id unikt for hver node — f.eks. kan man bruge pod-navnet. Hvis du bruger pod-navnet, skal du ikke glemme begrænsningen på 23 tegn for jboss-variabler, så det er bedre at bruge StatefulSet i stedet for Deployment.
Endnu en udfordring – hvis den fjernes eller genstartes, går dens cache tabt. Derfor er det vigtigt at indstille antallet af cacheejere til mindst to for alle cacher, så der forbliver en kopi af cachen. Løsningen er at køre ved opstart af pod'en, placer det i kataloget /opt/jboss/startup-scripts i containeren:
Indholdet af scriptet
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Indstiller CACHE_OWNERS til "${env.CACHE_OWNERS}" i alle cache-containere
/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-serverderefter skal du indstille værdien af miljøvariablen CACHE_OWNERS til den ønskede værdi.
Privat netværk med ip multicast understøttelse
Hvis du bruger Weavenet som CNI, vil multicast fungere med det samme – og dine Keycloak noder vil kunne se hinanden, så snart de er startet.
Hvis du ikke har understøttelse af ip multicast i Kubernetes klyngen, kan du konfigurere JGroups til at arbejde med andre protokoller til nodeopdagelse.
Den første mulighed er brugen af KUBE_DNS, som bruger headless service til at finde Keycloak noder; du skal blot videregive JGroups navnet på tjenesten, der skal bruges til at finde noder.
En anden mulighed er at anvende metoden KUBE_PING, som arbejder med API'et til at finde noder (du skal konfigurere serviceAccount med rettigheder list og get, hvorefter du skal konfigurere pods til at arbejde med denne serviceAccount).
Måde at finde noder for JGroups konfigureres ved at indstille miljøvariablerne JGROUPS_DISCOVERY_PROTOCOL og JGROUPS_DISCOVERY_PROPERTIES. For KUBE_PING du skal vælge pods ved at angive namespace og labels.
️ Hvis du bruger multicast og kører to eller flere Keycloak klynger i én Kubernetes klynge (for eksempel en i namespace
production, den anden —staging) — noderne i én Keycloak-klynge kan slutte sig til en anden klynge. Sørg for at bruge en unik multicast-adresse for hver klynge ved at indstille variablernejboss.default.multicast.addressogjboss.modcluster.multicast.addressiJAVA_OPTS.
Replikering mellem datacentre

Forbindelse
Keycloak anvender flere adskilte Infinispan cache-klustre for hvert datacenter, hvor Keycloak-klustre består af Keycloak-noder. Der er dog ingen forskel mellem Keycloak-noder i forskellige datacentre.
Keycloak-noderne bruger et eksternt Java Data Grid (Infinispan-servere) til kommunikation mellem datacentre. Kommunikationen fungerer via protokollen .
Infinispan-cache skal konfigureres med attributten remoteStore, så data kan gemmes i fjerndatakasser (i et andet datacenter, oversætterens bemærkning) cache. Der er separate infinispan-klustre blandt JDG-serverne, så data gemt på JDG1 på stedet site1 vil blive replikeret til JDG2 på stedet site2.
Og endelig underretter den modtagende JDG-server Keycloak-serverne i sin klynge via klientforbindelser, hvilket er en funktion ved HotRod-protokollen. Keycloak-noderne på site2 opdaterer deres Infinispan-cache, og den specifikke bruger-session bliver også tilgængelig på Keycloak-noderne på site2.
For nogle cache er det også muligt at undlade at lave sikkerhedskopier og helt undlade at skrive data via Infinispan-serveren. For dette skal indstillingen remote-store fjernes fra den specifikke Infinispan-cache (i filen standalone-ha.xml), hvorefter en specifik replicated-cache også ikke vil være nødvendig på Infinispan-serverens side.
Cache-konfiguration
Der er to typer cache i Keycloak:
Lokal. Den ligger tæt på databasen, reducerer belastningen på databasen samt svarforsinkelsen. Denne type cache gemmer realms, klienter, roller og brugermetadata. Denne type cache replikeres ikke, selvom den er en del af Keycloak-klyngen. Hvis en post ændres i cachen, sendes der en meddelelse om ændringen til de andre servere i klyngen, hvorefter posten fjernes fra cachen. Se beskrivelsen af
worknedenfor for en mere detaljeret beskrivelse af proceduren.Replikeret. Håndterer bruger-sessioner, offline tokens, og holder øje med login-fejl for at identificere phishing-forsøg og andre angreb. De gemte data i disse cache er midlertidige, lagres kun i RAM, men kan replikeres på tværs af klynge.
Infinispan-cache
Sessions — et koncept i Keycloak, separate caches, der kaldes authenticationSessions, anvendes til at gemme data for specifikke brugere. Anmodninger fra disse caches er typisk nødvendige for browseren og Keycloak-serverne, ikke for applikationer. Her viser afhængigheden af sticky sessions sig, og disse caches behøver ikke at blive replikeret, selv ikke i tilfælde af Active-Active tilstand.
Handlings tokens. En ny koncept, der typisk anvendes til forskellige scenarier, hvor en bruger eksempelvis skal gøre noget asynkront via e-mail. For eksempel under proceduren glemte adgangskode cache actionTokens anvendes til at spore metadata for tilknyttede tokens — for eksempel et token, der allerede er blevet brugt, og som ikke kan aktiveres igen. Denne type cache skal som regel replikeres mellem datacentre.
Caching og forældelse af gemte data arbejder for at reducere belastningen på databasen. Sådan caching forbedrer ydeevnen, men tilføjer et åbenlyst problem. Hvis en Keycloak-server opdaterer data, skal de andre servere have besked om det, så de kan opdatere dataene i deres caches. Keycloak bruger lokale caches realms, brugere og autorisation til at cache data fra databasen.
Der er også en separat cache work, som replikeres på tværs af alle datacentre. Den gemmer ikke nogen data fra databasen, men fungerer til at sende meddelelser om forældelse af data til clusterknuderne mellem datacentre. Med andre ord, så snart data opdateres, sender Keycloak-knuden en besked til de andre knuder i sit datacenter samt til knuderne i andre datacentre. Efter modtagelse af en sådan besked renser hver knude de relevante data i deres lokale caches.
Bruger sessioner. Cacherne med navne sessions, clientSessions, offlineSessions og offlineClientSessions, replikeres typisk mellem datacentre og er til opbevaring af data om bruger sessioner, der er aktive, mens brugeren er aktiv i browseren. Disse caches arbejder med applikationen, der behandler HTTP-anmodninger fra slutbrugerne, så de er relateret til sticky sessions og skal replikeres mellem datacentre.
Beskyttelse mod brute-force angreb. Cachen loginFailures tjenesten til at spore login-fejl, for eksempel hvor mange gange brugeren indtastede det forkerte password. Replikation af denne cache er administratorens ansvar. Men for at få en præcis optælling bør replikation aktiveres mellem datacentre. På den anden side, hvis disse data ikke replikeres, kan det forbedre ydeevnen, og hvis spørgsmålet opstår — kan replikation undlades.
Ved opsætning af Infinispan-klyngen skal cache-definitioner tilføjes til konfigurationsfilen:
Det er nødvendigt at konfigurere og starte Infinispan-klyngen, før Keycloak-klyngen startes.
Derefter skal der konfigureres remoteStore for Keycloak-cacher. For dette er det tilstrækkeligt med et script, der laves på samme måde som det tidligere, der bruges til at konfigurere variablen CACHE_OWNERS, det skal gemmes i en fil og placeres i katalogen. /opt/jboss/startup-scripts:
Indholdet af scriptet
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Opdater infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Tilføj fjernsocketbinding til 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 ** Opdater replicated-cache work element **
/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 ** Opdater distributed-cache sessions element **
/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 ** Opdater distributed-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 ** Opdater distributed-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 ** Opdater distributed-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 ** Opdater distributed-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 ** Opdater distributed-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 ** Opdater distributed-cache authenticationSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Opdater undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-serverHusk at installere JAVA_OPTS for Keycloak-noder for HotRod at fungere: remote.cache.host, remote.cache.port og service-navn jboss.site.name.
Links og yderligere dokumentation
Artiklen er oversat og forberedt til Habr af medarbejdere — intensiver, videokurser og virksomhedstræning fra praktikere (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Kilde: habr.com
