
TL; DR: va fi o descriere a Keycloak, un sistem open source de control al accesului, analiza structurii interne, detalii de configurare.
Introducere și idei cheie
În acest articol, vom vedea ideile de bază de care trebuie să țineți cont atunci când implementați un cluster Keycloak pe Kubernetes.
Dacă doriți să aflați mai multe despre Keycloak, consultați linkurile de la sfârșitul articolului. Pentru a deveni mai cufundat în practică, poți studia cu un modul care implementează ideile principale ale acestui articol (ghidul de lansare este acolo, acest articol va oferi o prezentare generală a dispozitivului și a setărilor, aproximativ traducător).
Keycloak este un sistem cuprinzător scris în Java și construit pe un server de aplicații . Pe scurt, este un cadru de autorizare care oferă utilizatorilor aplicației capabilități de federație și SSO (single sign-on).
Vă invităm să citiți oficialul sau pentru o înțelegere detaliată.
Lansarea Keycloak
Keycloak necesită două surse de date persistente pentru a rula:
- O bază de date utilizată pentru a stoca date stabilite, cum ar fi informații despre utilizator
- Cache-ul Datagrid, care este folosit pentru a stoca în cache datele din baza de date, precum și pentru a stoca unele metadate de scurtă durată și care se schimbă frecvent, cum ar fi sesiunile de utilizator. Implementat , care este de obicei semnificativ mai rapid decât baza de date. Dar, în orice caz, datele salvate în Infinispan sunt efemere - și nu trebuie să fie salvate nicăieri atunci când cluster-ul este repornit.
Keycloak funcționează în patru moduri diferite:
- Normal - un singur proces, configurat printr-un fișier standalone.xml
- Cluster obișnuit (opțiune de înaltă disponibilitate) - toate procesele trebuie să utilizeze aceeași configurație, care trebuie sincronizată manual. Setările sunt stocate într-un fișier standalone-ha.xml, în plus, trebuie să faceți acces partajat la baza de date și un echilibrator de încărcare.
- cluster de domenii — pornirea unui cluster în modul normal devine rapid o sarcină de rutină și plictisitoare pe măsură ce clusterul crește, deoarece de fiecare dată când se schimbă configurația, toate modificările trebuie făcute pe fiecare nod de cluster. Modul de operare al domeniului rezolvă această problemă prin configurarea unei locații de stocare partajată și publicarea configurației. Aceste setări sunt stocate în fișier domeniu.xml
- Replicare între centrele de date — dacă doriți să rulați Keycloak într-un cluster de mai multe centre de date, cel mai adesea în diferite locații geografice. În această opțiune, fiecare centru de date va avea propriul cluster de servere Keycloak.
În acest articol vom lua în considerare în detaliu a doua opțiune, adică cluster obișnuitși vom aborda puțin și subiectul replicării între centrele de date, deoarece este logic să rulăm aceste două opțiuni în Kubernetes. Din fericire, în Kubernetes nu există nicio problemă cu sincronizarea setărilor mai multor pod-uri (noduri Keycloak), deci cluster de domenii Nu va fi foarte greu de făcut.
De asemenea, vă rugăm să rețineți că cuvântul grup pentru restul articolului se va aplica numai unui grup de noduri Keycloak care lucrează împreună, nu este nevoie să faceți referire la un cluster Kubernetes.
Cluster obișnuit Keycloak
Pentru a rula Keycloak în acest mod, aveți nevoie de:
- configurați baza de date partajată externă
- instalați echilibrul de încărcare
- au o rețea internă cu suport IP multicast
Nu vom discuta despre configurarea unei baze de date externe, deoarece nu este scopul acestui articol. Să presupunem că există o bază de date funcțională undeva - și avem un punct de conectare la ea. Pur și simplu vom adăuga aceste date la variabilele de mediu.
Pentru a înțelege mai bine cum funcționează Keycloak într-un cluster de failover (HA), este important să știți cât de mult depinde totul de capacitățile de clustering ale Wildfly.
Wildfly folosește mai multe subsisteme, unele dintre ele sunt folosite ca echilibrator de încărcare, altele pentru toleranță la erori. Echilibratorul de încărcare asigură disponibilitatea aplicației atunci când un nod de cluster este supraîncărcat, iar toleranța la erori asigură disponibilitatea aplicației chiar dacă unele noduri de cluster eșuează. Unele dintre aceste subsisteme:
mod_cluster: Funcționează împreună cu Apache ca echilibrator de încărcare HTTP, depinde de multicast TCP pentru a găsi gazde în mod implicit. Poate fi înlocuit cu un echilibrator extern.infinispan: Un cache distribuit folosind canalele JGroups ca strat de transport. În plus, poate utiliza protocolul HotRod pentru a comunica cu un cluster extern Infinispan pentru a sincroniza conținutul cache-ului.jgroups: Oferă suport pentru comunicarea de grup pentru servicii foarte disponibile bazate pe canalele JGroups. Conductele numite permit ca instanțe de aplicație dintr-un cluster să fie conectate în grupuri, astfel încât comunicarea să aibă proprietăți precum fiabilitatea, ordinea și sensibilitatea la defecțiuni.
Echilibrarea greutății
Când instalați un echilibrator ca controler de intrare într-un cluster Kubernetes, este important să aveți în vedere următoarele lucruri:
Keycloak presupune că adresa de la distanță a clientului care se conectează prin HTTP la serverul de autentificare este adresa IP reală a computerului client. Setările de echilibrare și de intrare ar trebui să seteze corect antetele HTTP X-Forwarded-For и X-Forwarded-Protoși salvați, de asemenea, titlul original HOST. Ultima versiune ingress-nginx (> 0.22.0)
Activarea steagului proxy-address-forwarding prin setarea unei variabile de mediu PROXY_ADDRESS_FORWARDING в true dă Keycloak înțelegerea că funcționează în spatele unui proxy.
De asemenea, trebuie să activați sesiuni lipicioase în intrare. Keycloak folosește un cache distribuit Infinispan pentru a stoca datele asociate cu sesiunea de autentificare curentă și cu sesiunea utilizatorului. Cache-urile funcționează implicit cu un singur proprietar, cu alte cuvinte, acea anumită sesiune este stocată pe un nod din cluster, iar alte noduri trebuie să o interogheze de la distanță dacă au nevoie de acces la acea sesiune.
Mai exact, contrar documentației, atașarea unei sesiuni cu numele cookie nu a funcționat pentru noi
AUTH_SESSION_ID. Keycloak are o buclă de redirecționare, așa că vă recomandăm să alegeți un alt nume de cookie pentru sesiunea sticky.
Keycloak atașează și numele nodului la care a răspuns primul AUTH_SESSION_ID, și din moment ce fiecare nod din versiunea foarte disponibilă folosește aceeași bază de date, fiecare dintre ele un identificator de nod separat și unic pentru gestionarea tranzacțiilor. Se recomanda sa se introduca JAVA_OPTS parametrii jboss.node.name и jboss.tx.node.id unic pentru fiecare nod - puteți, de exemplu, să puneți numele podului. Dacă puneți un nume de pod, nu uitați de limita de 23 de caractere pentru variabilele jboss, deci este mai bine să utilizați un StatefulSet decât o Deployment.
Un alt rake - dacă podul este șters sau repornit, memoria cache a acestuia se pierde. Ținând cont de acest lucru, merită să setați numărul de proprietari de cache pentru toate cache-urile la cel puțin doi, astfel încât să rămână o copie a cache-ului. Soluția este să alergi la pornirea podului, plasându-l în director /opt/jboss/startup-scripts in recipient:
Conținutul scriptului
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-serverapoi setați valoarea variabilei de mediu CACHE_OWNERS la necesar.
Rețea privată cu suport IP multicast
Dacă utilizați Weavenet ca CNI, multicastul va funcționa imediat - iar nodurile voastre Keycloak se vor vedea reciproc de îndată ce sunt lansate.
Dacă nu aveți suport IP multicast în clusterul dvs. Kubernetes, puteți configura JGroups să funcționeze cu alte protocoale pentru a găsi noduri.
Prima opțiune este utilizarea KUBE_DNScare folosește headless service pentru a găsi nodurile Keycloak, pur și simplu treceți JGroups numele serviciului care va fi folosit pentru a găsi nodurile.
O altă opțiune este să folosiți metoda KUBE_PING, care funcționează cu API-ul pentru a căuta noduri (trebuie să configurați serviceAccount cu drepturi list и get, apoi configurați pod-urile să funcționeze cu aceasta serviceAccount).
Modul în care JGroups găsesc noduri este configurat prin setarea variabilelor de mediu JGROUPS_DISCOVERY_PROTOCOL и JGROUPS_DISCOVERY_PROPERTIES. Pentru KUBE_PING trebuie să selectați podurile întrebând namespace и labels.
️ Dacă utilizați multicast și rulați două sau mai multe clustere Keycloak într-un cluster Kubernetes (să spunem unul în spațiul de nume
production, al doilea -staging) - nodurile unui cluster Keycloak se pot alătura altui cluster. Asigurați-vă că utilizați o adresă multicast unică pentru fiecare cluster, setând variabilejboss.default.multicast.addressиjboss.modcluster.multicast.addressвJAVA_OPTS.
Replicare între centrele de date

Связь
Keycloak folosește mai multe clustere separate de cache Infinispan pentru fiecare centru de date unde se află clusterele Keycloack alcătuite din noduri Keycloak. Dar nu există nicio diferență între nodurile Keycloak din diferite centre de date.
Nodurile Keycloak folosesc o grilă de date Java externă (servere Infinispan) pentru comunicarea între centrele de date. Comunicarea funcționează conform protocolului .
Cache-urile Infinispan trebuie configurate cu atributul remoteStore, astfel încât datele să poată fi stocate de la distanță (în alt centru de date, aproximativ traducător) cache-urile. Există clustere infinispan separate între serverele JDG, astfel încât datele stocate pe JDG1 pe site site1 va fi replicat la JDG2 la fața locului site2.
Și, în cele din urmă, serverul JDG receptor notifică serverele Keycloak despre cluster-ul său prin conexiuni client, care este o caracteristică a protocolului HotRod. Nodurile Keycloak activate site2 își actualizează memoria cache Infinispan și sesiunea de utilizator specifică devine disponibilă și pe nodurile Keycloak activate site2.
Pentru unele cache, este, de asemenea, posibil să nu faceți copii de rezervă și să evitați în întregime scrierea datelor prin serverul Infinispan. Pentru a face acest lucru, trebuie să eliminați setarea remote-store memoria cache specifică Infinispan (în fișierul standalone-ha.xml), după care unele specifice replicated-cache De asemenea, nu va mai fi necesar pe partea serverului Infinispan.
Configurarea cache-urilor
Există două tipuri de cache în Keycloak:
Local. Este situat lângă baza de date și servește la reducerea încărcării bazei de date, precum și la reducerea latenței de răspuns. Acest tip de cache stochează domeniul, clienții, rolurile și metadatele utilizatorilor. Acest tip de cache nu este replicat, chiar dacă memoria cache face parte dintr-un cluster Keycloak. Dacă o intrare din cache se modifică, un mesaj despre modificare este trimis către serverele rămase din cluster, după care intrarea este exclusă din cache. Vezi descrierea
workVedeți mai jos pentru o descriere mai detaliată a procedurii.Replicat. Procesează sesiunile utilizatorilor, jetoanele offline și, de asemenea, monitorizează erorile de conectare pentru a detecta încercările de phishing cu parole și alte atacuri. Datele stocate în aceste cache-uri sunt temporare, stocate doar în RAM, dar pot fi replicate în cluster.
Cache-urile Infinispan
Sesiuni - un concept în Keycloak, numit cache separate authenticationSessions, sunt folosite pentru a stoca date ale anumitor utilizatori. Solicitările din aceste cache-uri sunt de obicei necesare browserului și serverelor Keycloak, nu aplicațiilor. Aici intervine dependența de sesiunile sticky, iar astfel de cache-uri în sine nu trebuie să fie replicate, chiar și în cazul modului Active-Active.
Jetoane de acțiune. Un alt concept, folosit de obicei pentru diverse scenarii când, de exemplu, utilizatorul trebuie să facă ceva asincron prin poștă. De exemplu, în timpul procedurii forget password ascunzătoare actionTokens folosit pentru a urmări metadatele token-urilor asociate - de exemplu, un token a fost deja folosit și nu poate fi activat din nou. Acest tip de cache trebuie de obicei replicat între centrele de date.
Memorarea în cache și îmbătrânirea datelor stocate lucrează pentru a ușura încărcarea bazei de date. Acest tip de cache îmbunătățește performanța, dar adaugă o problemă evidentă. Dacă un server Keycloak actualizează datele, celelalte servere trebuie să fie notificate astfel încât să poată actualiza datele din memoria cache. Keycloak folosește cache-urile locale realms, users и authorization pentru stocarea în cache a datelor din baza de date.
Există, de asemenea, un cache separat work, care este replicat în toate centrele de date. El însuși nu stochează date din baza de date, dar servește pentru a trimite mesaje despre îmbătrânirea datelor către nodurile de cluster dintre centrele de date. Cu alte cuvinte, de îndată ce datele sunt actualizate, nodul Keycloak trimite un mesaj altor noduri din centrul său de date, precum și nodurilor din alte centre de date. După primirea unui astfel de mesaj, fiecare nod șterge datele corespunzătoare din cache-urile sale locale.
Sesiuni de utilizator. Cache-uri cu nume sessions, clientSessions, offlineSessions и offlineClientSessions, sunt de obicei replicate între centrele de date și servesc la stocarea datelor despre sesiunile utilizatorilor care sunt active în timp ce utilizatorul este activ în browser. Aceste cache-uri funcționează cu aplicația care procesează solicitările HTTP de la utilizatorii finali, deci sunt asociate cu sesiuni sticky și trebuie replicate între centrele de date.
Protecție cu forța brută. Cache loginFailures Folosit pentru a urmări datele de eroare de conectare, cum ar fi de câte ori un utilizator a introdus o parolă incorectă. Replicarea acestui cache este responsabilitatea administratorului. Dar pentru un calcul precis, merită să activați replicarea între centrele de date. Dar, pe de altă parte, dacă nu replicați aceste date, veți îmbunătăți performanța și, dacă apare această problemă, este posibil ca replicarea să nu fie activată.
Când lansați un cluster Infinispan, trebuie să adăugați definiții cache în fișierul de setări:
<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" />Trebuie să configurați și să porniți clusterul Infinispan înainte de a porni clusterul Keycloak
Apoi trebuie să configurați remoteStore pentru cache-urile Keycloak. Pentru a face acest lucru, este suficient un script, care se face similar cu cel anterior, care este folosit pentru a seta variabila CACHE_OWNERS, trebuie să îl salvați într-un fișier și să îl puneți într-un director /opt/jboss/startup-scripts:
Conținutul scriptului
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-serverNu uitați să instalați JAVA_OPTS pentru ca nodurile Keycloak să ruleze HotRod: remote.cache.host, remote.cache.port și numele serviciului jboss.site.name.
Link-uri și documentație suplimentară
Articolul a fost tradus și pregătit pentru Habr de către angajați — cursuri intensive, cursuri video și formare corporativă de la specialiști în practică (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Sursa: www.habr.com
