Kør Keycloak i HA-tilstand på Kubernetes

Kør Keycloak i HA-tilstand på Kubernetes

TL; DR: der vil være en beskrivelse af Keycloak, et open source adgangskontrolsystem, en analyse af den interne enhed, konfigurationsdetaljer.

Introduktion og hovedideer

I denne artikel vil vi se de vigtigste ideer, du skal huske på, når du installerer en Keycloak-klynge oven på Kubernetes.

Hvis du vil vide mere om Keycloak, så se venligst linkene i slutningen af ​​artiklen. For at fordybe dig dybere i praksis, kan du studere vores depot med et modul, der implementerer hovedideerne i denne artikel (lanceringsvejledningen er der, i denne artikel vil der være en oversigt over enheden og indstillinger, ca. oversætter).

Keycloak er et komplekst system skrevet i Java og bygget oven på en applikationsserver. Vildflue. Kort sagt er det en autorisationsramme, der giver applikationsbrugere federation og SSO (single sign-on)-funktion.

Vi inviterer dig til at læse den officielle сайт eller Wikipedia for detaljeret forståelse.

Start Keycloak

Keycloak har brug for to vedvarende datakilder for at køre:

  • En database, der bruges til at gemme vedvarende data, såsom oplysninger om brugere
  • Datagrid cache, som bruges til at cache data fra databasen, samt til at gemme nogle kortvarige og hyppigt ændrede metadata, såsom brugersessioner. Udgivet Infinispan, som normalt er betydeligt hurtigere end databasen. Men under alle omstændigheder er de data, der er gemt i Infinispan, flygtige – og de behøver ikke at blive gemt et sted, når klyngen genstartes.

Keycloak fungerer i fire forskellige tilstande:

  • Normal - én og kun én proces, konfigureret gennem en fil standalone.xml
  • almindelig klynge (højt tilgængelig mulighed) - Alle processer skal bruge den samme konfiguration, som skal synkroniseres manuelt. Indstillinger gemmes i en fil standalone-ha.xml, derudover skal du lave en delt adgang til databasen og en load balancer.
  • Domæneklynge - at starte klyngen i normal tilstand bliver hurtigt en rutinemæssig og kedelig opgave, efterhånden som klyngen vokser, da hver gang du ændrer konfigurationen, skal du foretage alle ændringerne på hver node i klyngen. Domænedriftstilstanden løser dette problem ved at konfigurere noget delt lager og udgive konfigurationen. Disse indstillinger gemmes i en fil domæne.xml
  • Replikering mellem datacentre - hvis du ønsker at køre Keycloak i en klynge af flere datacentre, oftest forskellige geografiske steder. I denne mulighed vil hvert datacenter have sin egen klynge af Keycloak-servere.

I denne artikel vil vi se nærmere på den anden mulighed, dvs. normal klynge, samt et lille strejf af emnet replikering mellem datacentre, da det giver mening at køre disse to muligheder i Kubernetes. Heldigvis har Kubernetes ikke et problem med at synkronisere indstillingerne for flere pods (Keycloak noder), så domæneklynge det bliver ikke for svært at gøre.

Bemærk også, at ordet klynge indtil slutningen af ​​artiklen kun gælder for en gruppe Keycloak-knuder, der arbejder sammen, er der ingen grund til at henvise til en Kubernetes-klynge.

Almindelig Keycloak Cluster

For at køre Keycloak i denne tilstand skal du bruge:

  • oprette en ekstern delt database
  • installere belastningsbalancer
  • har et internt netværk med ip multicast-understøttelse

Vi vil ikke analysere konfigurationen af ​​den eksterne database, da det ikke er formålet med denne artikel. Lad os antage, at der et eller andet sted er en fungerende database - og vi har et forbindelsespunkt til den. Vi vil blot tilføje disse data til miljøvariablerne.

For bedre at forstå, hvordan Keycloak fungerer i en failover (HA) klynge, er det vigtigt at vide, hvor meget det hele afhænger af Wildflys klyngeevner.

Wildfly bruger flere undersystemer, nogle af dem bruges som en load balancer, nogle bruges til failover. Belastningsbalanceren sikrer tilgængeligheden af ​​applikationen, når klyngeknudepunktet er overbelastet, og failover sikrer tilgængeligheden af ​​applikationen, selvom nogle af klyngens noder fejler. Nogle af disse undersystemer er:

  • mod_cluster: fungerer sammen med Apache som en HTTP-belastningsbalancer, afhænger af TCP multicast til standard værtsopdagelse. Kan erstattes af en ekstern balancer.

  • infinispan: distribueret cache ved hjælp af JGroups-kanaler som transportlag. Eventuelt kan den bruge HotRod-protokollen til at kommunikere med en ekstern Infinispan-klynge for at synkronisere indholdet af cachen.

  • jgroups: Giver støtte til gruppeforeninger for højt tilgængelige tjenester baseret på JGroups-kanaler. Navngivne rør tillader applikationsforekomster i en klynge at blive forbundet i grupper, så forbindelsen har egenskaber såsom pålidelighed, orden og fejlfølsomhed.

belastningsbalancer

Når du installerer en balancer som en indgangscontroller i en Kubernetes-klynge, er det vigtigt at huske på følgende:

Keycloaks arbejde indebærer, at klientens fjernadresse, der forbinder via HTTP til autentificeringsserveren, er klientcomputerens rigtige IP-adresse. Balancer- og indgangsindstillinger skal indstille HTTP-headere korrekt X-Forwarded-For и X-Forwarded-Proto, og behold den originale titel HOST. nyeste version ingress-nginx (> 0.22.0) deaktiverer det som standard

Flag aktivering proxy-address-forwarding ved at indstille en miljøvariabel PROXY_ADDRESS_FORWARDING в true giver Keycloak forståelsen af, at den kører bag en proxy.

Du skal også aktivere klæbrige sessioner i indtrængen. Keycloak bruger Infinispans distribuerede cache til at gemme data forbundet med den aktuelle godkendelsessession og brugersession. Caches er som standard enkeltejere, med andre ord, den pågældende session er gemt på en klynge node, og andre noder skal anmode om det eksternt, hvis de har brug for adgang til den session.

Specifikt, i modsætning til dokumentationen, virkede det ikke for os at vedhæfte en session med cookienavnet AUTH_SESSION_ID. Keycloak har sløjfet omdirigeringen, så vi anbefaler at vælge et andet cookienavn til den klæbrige session.

Keycloak vedhæfter også navnet på den vært, der svarede først AUTH_SESSION_ID, og da hver node i den meget tilgængelige version bruger den samme database, hver af dem må have et separat og unikt node-id til styring af transaktioner. Det anbefales at sætte ind JAVA_OPTS parametre jboss.node.name и jboss.tx.node.id unikt for hver node - for eksempel kan du indstille navnet på poden. Hvis du sætter navnet på poden - glem ikke 23-tegnsgrænsen for jboss-variabler, så det er bedre at bruge StatefulSet, ikke Deployment.

Endnu en rake - hvis en pod slettes eller genstartes, går dens cache tabt. Med dette i tankerne er det værd at sætte antallet af cache-ejere for alle caches til mindst to, så der vil være en kopi af cachen. Løsningen er at køre manuskript til Wildfly når du starter poden, placer den i mappen /opt/jboss/startup-scripts i container:

Script indhold

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo * Setting CACHE_OWNERS to "${env.CACHE_OWNERS}" in all cache-containers

/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-server

Indstil derefter værdien af ​​miljøvariablen CACHE_OWNERS til det nødvendige.

Privat netværk med ip multicast-understøttelse

Hvis du bruger Weavenet som din CNI, vil multicast virke med det samme - og dine Keycloak-noder vil se hinanden, så snart de er oppe at køre.

Hvis du ikke har ip multicast-understøttelse i din Kubernetes-klynge, kan du konfigurere JGroups til at arbejde med andre protokoller for at finde noder.

Den første mulighed er at bruge KUBE_DNSsom bruger headless service for at finde Keycloak-noder, sender du blot JGroups navnet på den tjeneste, der skal bruges til at finde noderne.

En anden mulighed er at bruge metoden KUBE_PING, som arbejder med API'et til at finde noder (du skal konfigurere serviceAccount med rettigheder list и get, og konfigurer derefter pods til at arbejde med dette serviceAccount).

Hvordan noder søges efter JGroups konfigureres ved at indstille miljøvariabler JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Til KUBE_PING du skal vælge bælg ved at spørge namespace и labels.

️ Hvis du bruger multicast og kører to eller flere Keycloak-klynger i den samme Kubernetes-klynge (lad os sige en i navnerummet production, Sekundet - staging) - noder fra en Keycloak-klynge kan tilslutte sig en anden klynge. Sørg for at bruge en unik multicast-adresse for hver klynge ved at indstille variablerjboss.default.multicast.address и jboss.modcluster.multicast.address в JAVA_OPTS.

Replikering mellem datacentre

Kør Keycloak i HA-tilstand på Kubernetes

Link

Keycloak bruger flere separate Infinispan Cache Clusters for hvert datacenter, der hoster Keycloack-klynger, der består af Keycloak-noder. Men samtidig er der ingen forskel på Keycloak noder i forskellige datacentre.

Keycloak-noder bruger et eksternt Java Data Grid (Infinispan-servere) til at kommunikere mellem datacentre. Kommunikation fungerer i henhold til protokollen Infinispan HotRod.

Infinispan-caches skal konfigureres med attributten remoteStore, så dataene kan gemmes i fjernbetjeningen (i et andet datacenter, ca. oversætter) caches. Der er separate infinispan-klynger blandt JDG-serverne, så data gemt på JDG1 på stedet site1 vil blive replikeret til JDG2 på stedet site2.

Endelig giver den modtagende JDG-server besked til Keycloak-serverne om sin klynge via klientforbindelser, som er en funktion af HotRod-protokollen. Keycloak noder på site2 opdatere deres Infinispan-caches, og den pågældende brugersession bliver tilgængelig på Keycloak-noderne på site2.

Det er også muligt for nogle caches ikke at blive sikkerhedskopieret og helt at nægte at skrive data gennem Infinispan-serveren. For at gøre dette skal du fjerne indstillingen remote-store specifik Infinispan-cache (i fil standalone-ha.xml), hvorefter nogle specifikke replicated-cache vil heller ikke længere være nødvendig på siden af ​​Infinispan-serveren.

Opsætning af caches

Der er to typer caches i Keycloak:

  • Lokal. Den er placeret ved siden af ​​basen, tjener til at reducere belastningen på databasen, samt til at reducere svarforsinkelse. Denne type cache gemmer realm, klienter, roller og brugermetadata. Denne type cache er ikke replikeret, selvom denne cache er en del af en Keycloak-klynge. Hvis en post i cachen ændres, sendes en ændringsmeddelelse til resten af ​​serverne i klyngen, hvorefter posten udelukkes fra cachen. se beskrivelse work nedenfor for en mere detaljeret beskrivelse af proceduren.

  • Replikerbar. Behandler brugersessioner, offline-tokens og overvåger login-fejl for at registrere adgangskode-phishing-forsøg og andre angreb. Dataene, der er gemt i disse caches, er midlertidige, gemmes kun i RAM, men kan replikeres på tværs af klyngen.

Infinispan-cacher

session - et koncept i Keycloak, separate caches, som kaldes authenticationSessions, bruges til at gemme data fra specifikke brugere. Anmodninger fra disse caches er normalt nødvendige af browseren og Keycloak-servere, ikke applikationer. Det er her, afhængigheden af ​​klæbrige sessioner manifesterer sig, og sådanne caches i sig selv behøver ikke at blive replikeret, selv i tilfælde af Active-Active-tilstand.

Handlingstokens. Et andet koncept, der normalt bruges til forskellige scenarier, når brugeren for eksempel skal gøre noget asynkront via mail. For eksempel under proceduren forget password cache actionTokens bruges til at spore metadata for relaterede tokens - for eksempel er tokenet allerede blevet brugt og kan ikke genaktiveres. Denne type cache skal typisk replikeres mellem datacentre.

Caching og udløb af lagrede data arbejder for at fjerne belastningen fra databasen. Denne caching forbedrer ydeevnen, men tilføjer et åbenlyst problem. Hvis én Keycloak-server opdaterer dataene, skal resten af ​​serverne underrettes, så de kan opdatere deres cache. Keycloak bruger lokale caches realms, users и authorization til cachelagring af data fra databasen.

Der er også en separat cache work, som er replikeret på tværs af alle datacentre. Det gemmer ikke selv nogen data fra databasen, men tjener til at sende dataaldringsmeddelelser til klynge noder mellem datacentre. Med andre ord, så snart dataene er opdateret, sender Keycloak-knuden en besked til andre noder i sit datacenter, såvel som noder i andre datacentre. Ved modtagelse af en sådan besked renser hver knude de tilsvarende data i sine lokale caches.

Bruger sessioner. Caches med navne sessions, clientSessions, offlineSessions и offlineClientSessions, replikeres normalt mellem datacentre og tjener til at gemme data om brugersessioner, der er aktive, mens brugeren er aktiv i browseren. Disse caches arbejder med den applikation, der håndterer HTTP-anmodninger fra slutbrugere, så de er forbundet med klæbrige sessioner og skal replikeres mellem datacentre.

brute force beskyttelse. Cache loginFailures bruges til at spore login-fejldata, såsom antallet af gange, en bruger indtastede en forkert adgangskode. Replikering af denne cache er op til administratoren. Men for en nøjagtig beregning er det værd at aktivere replikering mellem datacentre. Men på den anden side, hvis du ikke replikerer disse data, vil du være i stand til at forbedre ydeevnen, og hvis dette spørgsmål opstår, bliver replikering muligvis ikke aktiveret.

Når du udruller en Infinispan-klynge, skal du tilføje cachedefinitioner til indstillingsfilen:

<replicated-cache-configuration name="keycloak-sessions" mode="ASYNC" start="EAGER" batching="false">
</replicated-cache-configuration>

<replicated-cache name="work" configuration="keycloak-sessions" />
<replicated-cache name="sessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineSessions" configuration="keycloak-sessions" />
<replicated-cache name="actionTokens" configuration="keycloak-sessions" />
<replicated-cache name="loginFailures" configuration="keycloak-sessions" />
<replicated-cache name="clientSessions" configuration="keycloak-sessions" />
<replicated-cache name="offlineClientSessions" configuration="keycloak-sessions" />

Du skal konfigurere og starte Infinispan-klyngen, før du kører Keycloak-klyngen

Så skal du indstille remoteStore til Keycloak-cacher. Til dette er et script nok, som udføres på samme måde som det forrige, som bruges til at indstille variablen CACHE_OWNERS, skal du gemme den i en fil og lægge den i en mappe /opt/jboss/startup-scripts:

Script indhold

embed-server --server-config=standalone-ha.xml --std-out=echo
batch

echo *** Update infinispan subsystem ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)

echo ** Add remote socket binding to 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 ** Update 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 ** Update 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 ** Update 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 ** Update 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 ** Update 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 ** Update 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 ** Update 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 ** Update distributed-cache authenticationSessions element **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

echo *** Update undertow subsystem ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)

run-batch
stop-embedded-server

Glem ikke at installere JAVA_OPTS for at Keycloak-noder fungerer HotRod: remote.cache.host, remote.cache.port og servicenavn jboss.site.name.

Links og yderligere dokumentation

Artiklen er oversat og udarbejdet til Habr af medarbejdere Slurm træningscenter — intensive, videokurser og virksomhedstræning fra praktikere (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Kilde: www.habr.com

Tilføj en kommentar