Avviamo Keycloak in modalità HA su Kubernetes

Avviamo Keycloak in modalità HA su Kubernetes

TL;DR: sarà una descrizione di Keycloak, un sistema di controllo degli accessi open source, analisi della sua struttura interna, dettagli di configurazione.

Introduzione e idee principali

In questo articolo esamineremo le idee principali da tenere a mente durante il deployment di un cluster Keycloak su Kubernetes.

Se desiderate saperne di più su Keycloak, consultate i link alla fine dell'articolo. Per un'immersione più pratica, potete esplorare il nostro repository con il modulo che implementa le idee principali di questo articolo (la guida al funzionamento è disponibile allo stesso modo, in questo articolo sarà fornita una panoramica della struttura e delle impostazioni, nota del traduttore).

Keycloak è un sistema complesso, scritto in Java e costruito su un server di applicazioni Wildfly. In breve, è un framework per l'autenticazione che offre agli utenti delle applicazioni la federazione e la possibilità di SSO (single sign-on).

Vi invitiamo a leggere la documentazione ufficiale sito o Wikipedia per una comprensione più dettagliata.

Avvio di Keycloak

Per Keycloak sono necessari due fonti di dati permanenti per funzionare:

  • Un database utilizzato per memorizzare dati consolidati, come informazioni sugli utenti.
  • Il Datagrid cache, utilizzato per memorizzare nella cache i dati del database e per conservare alcuni metadati a breve termine e frequentemente modificati, come le sessioni utente. Rilasciato Infinispan, che è generalmente molto più veloce di un database. Tuttavia, i dati memorizzati in Infinispan sono effimeri e non devono essere conservati al riavvio del cluster.

Keycloak opera in quattro modalità diverse:

  • Normale — un solo processo, configurato tramite il file standalone.xml
  • Un cluster normale (variante ad alta disponibilità) — tutti i processi devono utilizzare la stessa configurazione, che deve essere sincronizzata manualmente. Le impostazioni sono memorizzate nel file standalone-ha.xml, è necessario inoltre configurare l'accesso comune al database e un bilanciatore di carico.
  • Cluster di dominio — l'avvio di un cluster in modalità normale diventa rapidamente una routine noiosa man mano che il cluster cresce, poiché ogni volta che si modifica la configurazione è necessario apportare tutte le modifiche su ciascun nodo del cluster. La modalità di dominio risolve questo problema creando uno spazio di archiviazione comune per la configurazione e la sua pubblicazione. Queste impostazioni sono memorizzate nel file domain.xml
  • Replica tra data center — nel caso si desideri avviare Keycloak in un cluster di più data center, spesso situati in diverse aree geografiche. In questa modalità, ogni data center avrà il proprio cluster di server Keycloak.

In questo articolo esamineremo nel dettaglio la seconda opzione, ovvero un cluster normale, e tratteremo brevemente anche il tema della replica tra data center, poiché queste due modalità hanno senso negli ambienti Kubernetes. Fortunatamente, in Kubernetes non ci sono problemi di sincronizzazione delle impostazioni tra più pod (nodi Keycloak), quindi il cluster di dominio non sarà particolarmente difficile da realizzare.

Si prega inoltre di notare che la parola un cluster fino alla fine dell'articolo si applicherà esclusivamente al gruppo di nodi Keycloak che operano insieme, non è necessario fare riferimento al cluster Kubernetes.

Cluster Keycloak standard

Per avviare Keycloak in questa modalità è necessario:

  • configurare un database esterno condiviso
  • installare un bilanciatore di carico
  • disporre di una rete interna con supporto per ip multicast

Non tratteremo la configurazione del database esterno, poiché non è l'obiettivo di questo articolo. Supponiamo che ci sia un database funzionante da qualche parte e che abbiamo un punto di collegamento. Aggiungeremo semplicemente questi dati nelle variabili d'ambiente.

Per una migliore comprensione di come Keycloak funzioni in un cluster ad alta disponibilità (HA), è importante sapere quanto questo dipenda dalle capacità di clustering di Wildfly.

Wildfly utilizza diversi sottosistemi, alcuni dei quali vengono impiegati come bilanciatore di carico, altri per l'alta disponibilità. Il bilanciatore di carico garantisce la disponibilità dell'applicazione in caso di sovraccarico di un nodo del cluster, mentre l'alta disponibilità assicura l'accessibilità dell'applicazione anche in caso di guasto di alcuni nodi del cluster. Alcuni di questi sottosistemi:

  • mod_cluster: funziona insieme ad Apache come bilanciatore HTTP, utilizzando il multicast TCP per individuare i nodi per impostazione predefinita. Può essere sostituito da un bilanciatore esterno.

  • infinispan: cache distribuita che utilizza i canali JGroups come livello di trasporto. Può anche utilizzare il protocollo HotRod per comunicare con un cluster esterno di Infinispan per la sincronizzazione dei contenuti della cache.

  • jgroups: fornisce supporto per la comunicazione di gruppo per servizi ad alta disponibilità basati su canali JGroups. I canali nominati consentono alle istanze dell'applicazione nel cluster di connettersi in gruppi, garantendo proprietà di comunicazione come affidabilità, ordinamento e sensibilità ai guasti.

Bilanciatore di carico

Quando si installa un bilanciatore come controller ingress in un cluster Kubernetes, è importante tenere a mente le seguenti considerazioni:

Il funzionamento di Keycloak presuppone che l'indirizzo remoto del client, connesso tramite HTTP al server di autenticazione, sia il vero indirizzo IP del computer client. Le impostazioni del bilanciatore e dell'ingress devono configurare correttamente gli header HTTP X-Forwarded-For e X-Forwarded-Proto, e mantenere l'header originale HOST. Ultima versione ingress-nginx (> 0.22.0) disattiva questo per impostazione predefinita

Attivazione del flag proxy-address-forwarding impostando la variabile d'ambiente PROXY_ADDRESS_FORWARDING in true fornisce a Keycloak la comprensione di essere in funzione dietro un proxy.

È inoltre necessario abilitare sticky sessions in ingress. Keycloak utilizza una cache distribuita Infinispan per mantenere i dati relativi alla sessione di autenticazione attuale e alla sessione utente. Le cache operano con un singolo proprietario per impostazione predefinita, in altre parole, questa specifica sessione viene mantenuta su un nodo del cluster, mentre gli altri nodi devono richiederla da remoto se necessitano di accedere a questa sessione.

In particolare, contrariamente alla documentazione, non ha funzionato l'allegamento della sessione con il nome cookie AUTH_SESSION_ID. Keycloak ha provocato un loop di reindirizzamento, quindi si consiglia di scegliere un altro nome cookie per la sticky session.

Inoltre, Keycloak allega il nome del nodo che ha risposto per primo a AUTH_SESSION_ID, e poiché ogni nodo nell'opzione ad alta disponibilità utilizza lo stesso database, ciascuno di essi deve avere un identificatore nodo separato e unico per gestire le transazioni. Si raccomanda di impostare in JAVA_OPTS parametri jboss.node.name e jboss.tx.node.id unici per ogni nodo — ad esempio, è possibile impostare il nome del pod. Se decidi di impostare il nome del pod, ricorda il limite di 23 caratteri per le variabili jboss; quindi è meglio utilizzare StatefulSet invece di Deployment.

Un altro problema è che se il pod viene eliminato o riavviato, la sua cache viene persa. Tenendo conto di ciò, è consigliabile impostare il numero di proprietari della cache per tutte le cache a almeno due, in modo da mantenere una copia della cache. La soluzione è avviare uno script per Wildfly all'avvio del pod, posizionandolo nella directory /opt/jboss/startup-scripts nel contenitore:

Il contenuto dello script

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

echo * Impostazione di CACHE_OWNERS su "${env.CACHE_OWNERS}" in tutti i cache-container

/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

dopo di che impostare il valore della variabile d'ambiente CACHE_OWNERS come richiesto.

Rete privata con supporto ip multicast

Se utilizzi Weavenet come CNI, il multicast funzionerà immediatamente e i tuoi nodi Keycloak si vedranno tra loro non appena saranno avviati.

Se non hai supporto per ip multicast nel cluster Kubernetes, puoi configurare JGroups per utilizzare altri protocolli per la scoperta dei nodi.

La prima opzione è l'uso di KUBE_DNS, che utilizza servizio headless Per cercare i nodi Keycloak, devi semplicemente passare a JGroups il nome del servizio, che verrà utilizzato per la ricerca dei nodi.

Un'altra opzione è l'uso del metodo KUBE_PING, che funziona con l'API per la ricerca dei nodi (devi configurare il serviceAccount con i permessi list e get, dopodiché configurare i pod per lavorare con questo. serviceAccount).

Il modo di cercare i nodi per JGroups è configurato impostando le variabili d'ambiente JGROUPS_DISCOVERY_PROTOCOL e JGROUPS_DISCOVERY_PROPERTIES. Per KUBE_PING devi selezionare i pod specificando namespace e etichette.

️ Se utilizzi multicast e lanci due o più cluster Keycloak in un unico cluster Kubernetes (ad esempio uno nel namespace produzione, il secondo — staging) — i nodi di un cluster Keycloak possono unirsi a un altro cluster. Assicurati di utilizzare un indirizzo multicast unico per ogni cluster impostando le variabilijboss.default.multicast.address e jboss.modcluster.multicast.address in JAVA_OPTS.

Replica tra data center

Avviamo Keycloak in modalità HA su Kubernetes

Comunicazione

Keycloak utilizza molteplici cluster di cache Infinispan separati per ciascun data center dove sono situati i cluster Keycloak, composti da nodi Keycloak. Tuttavia, non c'è differenza tra i nodi Keycloak in diversi data center.

I nodi Keycloak utilizzano una Griglia Dati Java esterna (server Infinispan) per la comunicazione tra data center. La comunicazione avviene tramite il protocollo Infinispan HotRod.

I cache Infinispan devono essere configurati con l'attributo remoteStore, affinché i dati possano essere memorizzati in cache remote (in un altro centro dati, nota del traduttore) ci sono cluster separati di infinispan tra i server JDG, quindi i dati memorizzati su JDG1 nel sito site1 saranno replicati su JDG2 nel sito site2.

Infine, il server JDG notifica i server Keycloak del proprio cluster tramite connessioni client, che è una peculiarità del protocollo HotRod. I nodi Keycloak su site2 aggiornano i loro cache Infinispan, e la specifica sessione utente diventa accessibile anche sui nodi Keycloak su site2.

Per alcune cache, è anche possibile non eseguire backup e rinunciare completamente alla registrazione dei dati tramite il server Infinispan. Per fare ciò, è necessario rimuovere la configurazione remote-store da una specifica cache Infinispan (nel file standalone-ha.xml), dopo di che una specifica replicated-cache non sarà più necessaria sul lato del server Infinispan.

Configurazione delle cache

Ci sono due tipi di cache in Keycloak:

  • Locale. È collocato vicino al database, serve a ridurre il carico sul database e a diminuire la latenza. In questo tipo di cache vengono memorizzati realm, clienti, ruoli e metadati utente. Questo tipo di cache non è replicato, anche se fa parte di un cluster Keycloak. Quando viene modificato un record nella cache, viene inviata una notifica agli altri server del cluster riguardo al cambiamento e il record viene rimosso dalla cache. Vedi descrizione work di seguito, per una descrizione più dettagliata della procedura.

  • Replicabile. Gestisce le sessioni utente, i token offline e monitora gli errori di accesso per rilevare tentativi di phishing delle password e altre attacchi. I dati memorizzati in queste cache sono temporanei, vengono mantenuti solo in memoria volatile, ma possono essere replicati nel cluster.

Cache Infinispan

Sessioni — concetto in Keycloak, cache separate note come authenticationSessions, utilizzati per memorizzare i dati specifici degli utenti. Le richieste provenienti da queste cache sono tipicamente necessarie ai browser e ai server Keycloak, ma non alle applicazioni. Qui si manifesta la dipendenza dalle sessioni sticky, e tali cache non devono essere replicate, anche in modalità Active-Active.

Token di azione. Un'altra concettualizzazione, generalmente impiegata in vari scenari in cui, per esempio, un utente deve effettuare un'azione in modo asincrono via email. Ad esempio, durante la procedura dimentica la password cache actionTokens utilizzato per monitorare i metadati dei token correlati — ad esempio, un token è stato già utilizzato e non può essere attivato nuovamente. Questo tipo di cache deve generalmente essere replicato tra i data center.

Caching e scadenza dei dati memorizzati serve a ridurre il carico sul database. Tale caching migliora le prestazioni, ma comporta un evidente problema. Se un server Keycloak aggiorna i dati, gli altri server devono essere informati in modo da poter aggiornare i dati nelle loro cache. Keycloak utilizza cache locali realms, users e autorizzazione per il caching dei dati dal database.

C'è anche una cache separata work, che viene replicata in tutti i data center. Essa non memorizza dati dal database, ma serve per inviare messaggi di invalidamento dati ai nodi del cluster tra i data center. In altre parole, non appena i dati vengono aggiornati, il nodo Keycloak invia un messaggio agli altri nodi nel suo data center, nonché ai nodi di altri data center. Dopo aver ricevuto tale messaggio, ogni nodo pulisce i dati pertinenti nelle proprie cache locali.

Sessioni utente. Le cache con i nomi sessioni, clientSessions, offlineSessions e offlineClientSessions, vengono solitamente replicate tra i data center e servono per memorizzare i dati sulle sessioni utente attive durante l'attività dell'utente nel browser. Queste cache lavorano con l'applicazione che gestisce le richieste HTTP dagli utenti finali, quindi sono collegate alle sticky sessions e devono essere replicate tra i data center.

Protezione contro gli attacchi di forza bruta. La cache loginFailures serve a monitorare i dati sugli errori di accesso, ad esempio quante volte un utente ha inserito una password errata. La replicazione di questa cache è compito dell'amministratore. Tuttavia, per un conteggio preciso è consigliabile attivare la replicazione tra i data center. D'altra parte, se non si replicano questi dati, si può migliorare le prestazioni, e se sorge questo interrogativo — la replicazione può anche non essere attivata.

Durante la distribuzione del cluster Infinispan, è necessario aggiungere le definizioni delle cache nel file di configurazione:

È necessario configurare e avviare il cluster Infinispan prima di avviare il cluster Keycloak.

Dopo, è necessario configurare remoteStore per le cache di Keycloak. Per questo è sufficiente uno script, realizzato in modo analogo a quello precedente, utilizzato per impostare la variabile CACHE_OWNERS, deve essere salvato in un file e posizionato nella directory /opt/jboss/startup-scripts:

Il contenuto dello script

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

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

echo ** Aggiungi il binding socket remoto al server infinispan **
/socket-binding-group=standard-sockets/remote-destination-outbound-socket-binding=remote-cache:add(host=${remote.cache.host:localhost}, port=${remote.cache.port:11222})

echo ** Aggiorna l'elemento di lavoro della cache replicata **
/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 ** Aggiorna l'elemento sessioni della cache distribuita **
/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 ** Aggiorna l'elemento offlineSessions della cache distribuita **
/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 ** Aggiorna l'elemento clientSessions della cache distribuita **
/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 ** Aggiorna l'elemento offlineClientSessions della cache distribuita **
/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 ** Aggiorna l'elemento loginFailures della cache distribuita **
/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 ** Aggiorna l'elemento actionTokens della cache distribuita **
/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 ** Aggiorna l'elemento authenticationSessions della cache distribuita **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)

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

run-batch
stop-embedded-server

Non dimenticate di impostare JAVA_OPTS per i nodi Keycloak per far funzionare HotRod: remote.cache.host, remote.cache.port e il nome del servizio jboss.site.name.

Link e documentazione aggiuntiva

L'articolo è stato tradotto e preparato per Habr dai collaboratori del centro di formazione Slërm — corsi intensivi, video corsi e formazione aziendale da professionisti esperti (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster