
TL;DR: będzie opis Keycloak, systemu kontroli dostępu o otwartym kodzie źródłowym, analiza wewnętrznej struktury, szczegóły konfiguracji.
Wprowadzenie i podstawowe idee
W tym artykule przyjrzymy się podstawowym pomysłom, które należy pamiętać podczas wdrażania klastra Keycloak na Kubernetes.
Jeśli chcesz poznać Keycloak bardziej szczegółowo — skorzystaj z linków na końcu artykułu. Aby bardziej zagłębić się w praktykę — możesz zapoznać się z z modułem, który wdraża podstawowe idee tego artykułu (przewodnik uruchomienia znajduje się tam, w tym artykule znajdziesz przegląd struktury i konfiguracji, przyp. tłumacza).
Keycloak to kompleksowy system napisany w Javie, działający na serwerze aplikacyjnym . W skrócie, jest to framework do autoryzacji, który daje użytkownikom aplikacji federacyjność i możliwość SSO (single sign-on).
Zachęcamy do przeczytania oficjalnej lub w celu dokładnego zrozumienia.
Uruchamianie Keycloak
Aby uruchomić Keycloak, potrzebne są dwa trwale przechowywane źródła danych:
- Baza danych, używana do przechowywania ustalonych danych, na przykład informacji o użytkownikach
- Datagrid cache, który jest używany do buforowania danych z bazy oraz do przechowywania niektórych krótkoterminowych i często zmieniających się metadanych, na przykład sesji użytkowników. Realizowane przez , które zazwyczaj jest znacznie szybsze od bazy danych. Jednak w każdym przypadku dane przechowywane w Infinispan są efemeryczne — i nie muszą być zachowywane gdziekolwiek przy ponownym uruchomieniu klastra.
Keycloak działa w czterech różnych trybach:
- Zwykły — jeden i tylko jeden proces, konfigurowany za pomocą pliku standalone.xml
- Zwykły klaster (wysokodostępna wersja) — wszystkie procesy muszą korzystać z tej samej konfiguracji, którą należy zsynchronizować ręcznie. Ustawienia są przechowywane w pliku standalone-ha.xml, dodatkowo należy zapewnić wspólny dostęp do bazy danych oraz load balancer.
- Klaster domenowy — uruchamianie klastra w zwykłym trybie szybko staje się rutynnowym i nudnym zadaniem przy wzroście klastra, gdyż za każdym razem przy zmianie konfiguracji należy wprowadzić wszystkie zmiany na każdym węźle klastra. Tryb pracy domenezny rozwiązuje ten problem poprzez ustawienie wspólnego miejsca przechowywania i publikacji konfiguracji. Ustawienia te są przechowywane w pliku domain.xml
- Replikacja między centrami danych — w przypadku, gdy chcą Państwo uruchomić Keycloak w klastrze złożonym z kilku centrów danych, często w różnych lokalizacjach geograficznych. W tej konfiguracji każde centrum danych będzie miało własny klaster serwerów Keycloak.
W tym artykule dokładnie omówimy drugi wariant, to znaczy zwykły klaster, a także w krótkim stopniu poruszymy temat replikacji między centrami danych, ponieważ te dwa warianty mają sens uruchamiać w Kubernetes. Na szczęście w Kubernetes nie ma problemu z synchronizacją ustawień wielu podów (węzłów Keycloak), więc klaster domenowy będzie stosunkowo łatwy do zrealizowania.
Proszę również zwrócić uwagę, że słowo klaster do końca artykułu będzie stosowane wyłącznie w odniesieniu do grupy węzłów Keycloak działających razem, nie ma potrzeby odnosić się do klastra Kubernetes.
Zwykły klaster Keycloak
Aby uruchomić Keycloak w tym trybie, należy:
- skonfigurować zewnętrzną wspólną bazę danych
- zainstalować load balancer
- posiadać wewnętrzną sieć z obsługą multicast IP
Nie będziemy omawiać konfiguracji zewnętrznej bazy danych, ponieważ nie jest to celem tego artykułu. Załóżmy, że gdzieś istnieje działająca baza danych — i mamy do niej punkt połączenia. Po prostu dodamy te dane do zmiennych środowiskowych.
Aby lepiej zrozumieć, jak Keycloak działa w klastrze odpornym na awarie (HA), ważne jest, aby wiedzieć, jak bardzo to wszystko zależy od możliwości Wildfly w zakresie klastrowania.
Wildfly stosuje kilka podsystemów, niektóre z nich są wykorzystywane jako load balancer, inne — do zapewnienia dostępności. Load balancer zapewnia dostępność aplikacji w przypadku przeciążenia węzła klastra, a odporność na awarie gwarantuje dostępność aplikacji nawet w przypadku awarii części węzłów klastra. Niektóre z tych podsystemów to:
mod_cluster: działa współpracując z Apache jako load balancer HTTP, zależy od multicast TCP do znajdowania węzłów domyślnie. Może być zastąpiony zewnętrznym load balancerem.infinispan: rozproszona pamięć podręczna, która wykorzystuje kanały JGroups jako poziom transportowy. Dodatkowo może stosować protokół HotRod do komunikacji z zewnętrznym klastrem Infinispan w celu synchronizacji zawartości pamięci podręcznej.jgroups: zapewnia wsparcie dla komunikacji grupowej w wysoko dostępnych serwisach opartych na kanałach JGroups. Nazwane kanały umożliwiają instancjom aplikacji w klastrze łączenie się w grupy, tak że komunikacja ma takie właściwości, jak niezawodność, uporządkowanie, czułość na awarie.
Rozkładarka obciążenia
Przy instalacji rozkładarki obciążenia jako kontrolera ingress w klastrze Kubernetes ważne jest, aby mieć na uwadze następujące kwestie:
Działanie Keycloak zakłada, że zdalny adres klienta, łączącego się za pomocą HTTP z serwerem autoryzacji, jest rzeczywistym adresem IP komputera klienta. Ustawienia rozkładarki i ingress muszą poprawnie ustawiać nagłówki HTTP X-Forwarded-For i X-Forwarded-Proto, a także zachowywać oryginalny nagłówek HOST. Ostatnia wersja ingress-nginx (> 0.22.0)
Aktywacja flagi proxy-address-forwarding poprzez ustawienie zmiennej środowiskowej PROXY_ADDRESS_FORWARDING do true daje Keycloak zrozumienie, że działa za proxy.
Należy również włączyć sesje sticky w ingress. Keycloak używa rozproszonych pamięci podręcznych Infinispan do przechowywania danych związanych z bieżącą sesją autoryzacji i sesją użytkownika. Pamięci podręczne działają z jednym właścicielem domyślnie, innymi słowy, ta konkretna sesja jest przechowywana na pewnym węźle klastra, a inne węzły muszą żądać jej zdalnie, jeśli będą potrzebowały dostępu do tej sesji.
Konkretne u nas, wbrew dokumentacji, nie zadziałało przypięcie sesji z nazwą ciasteczka
AUTH_SESSION_ID. Keycloak zapętlił przekierowanie, dlatego zalecamy wybranie innej nazwy ciasteczka dla sticky session.
Również Keycloak dołącza nazwę węzła, który odpowiedział pierwszy, do AUTH_SESSION_ID, a ponieważ każdy węzeł w wariancie wysokiej dostępności korzysta z tej samej bazy danych, każdy z nich oddzielny i unikalny identyfikator węzła do zarządzania transakcjami. Zaleca się ustawienie w JAVA_OPTS parametrów jboss.node.name i jboss.tx.node.id unikalnymi dla każdego węzła — można na przykład ustawić nazwę poda. Jeśli zdecydujecie się na ustawienie nazwy poda — nie zapomnijcie o ograniczeniu do 23 znaków dla zmiennych jboss, więc lepiej używać StatefulSet, a nie Deployment.
Jeszcze jedna pułapka — jeśli pod jest usuwany lub restartowany, jego pamięć podręczna jest tracona. W związku z tym warto ustawić liczbę właścicieli pamięci podręcznej dla wszystkich pamięci podręcznych na co najmniej dwa, aby pozostała kopia pamięci podręcznej. Rozwiązaniem jest uruchomienie przy uruchamianiu poda, umieszczając go w katalogu /opt/jboss/startup-scripts w kontenerze:
Zawartość skryptu
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo * Ustawienie CACHE_OWNERS na "${env.CACHE_OWNERS}" we wszystkich kontenerach pamięci podręcznej
/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-serverpo czym ustawić wartość zmiennej środowiskowej CACHE_OWNERS na wymaganą.
Prywatna sieć z obsługą ip multicast
Jeśli używasz Weavenet jako CNI, multicast będzie działać od razu — a Twoje węzły Keycloak będą widzieć się nawzajem, jak tylko zostaną uruchomione.
Jeśli nie masz wsparcia dla ip multicast w klastrze Kubernetes, możesz skonfigurować JGroups, aby działał z innymi protokołami służącymi do znajdowania węzłów.
Pierwsza opcja — użycie KUBE_DNS, który wykorzystuje headless service do znajdowania węzłów Keycloak, po prostu przekazujesz JGroups nazwę usługi, która będzie używana do znajdowania węzłów.
Inną opcją — zastosowanie metody KUBE_PING, która działa z API do znajdowania węzłów (trzeba skonfigurować serviceAccount z uprawnieniami list i get, po czym skonfigurować pady do współpracy z tą serviceAccount).
Metoda znajdowania węzłów dla JGroups jest konfigurowana przez ustawienie zmiennych środowiskowych JGROUPS_DISCOVERY_PROTOCOL i JGROUPS_DISCOVERY_PROPERTIES. Dla KUBE_PING trzeba wybrać pady, określając namespace i labels.
️ Jeśli używasz multicast i uruchamiasz dwa lub więcej klastrów Keycloak w jednym klastrze Kubernetes (powiedzmy jeden w namespace
production, drugi —staging) — węzły jednego klastra Keycloak mogą dołączyć do innego klastra. Zawsze używaj unikalnego adresu multicast dla każdego klastra, ustawiając zmiennejboss.default.multicast.addressijboss.modcluster.multicast.addressdoJAVA_OPTS.
Replikacja między centrami danych

Łączność
Keycloak używa wielu osobnych klastrów pamięci podręcznej Infinispan dla każdego centrum danych, w którym znajdują się klastry Keycloack, składające się z węzłów Keycloak. Jednak nie ma różnicy pomiędzy węzłami Keycloak w różnych centrach danych.
Węzły Keycloak używają zewnętrznej Java Data Grid (serwery Infinispan) do komunikacji między centrami danych. Połączenie działa w protokole .
Pamięci podręczne Infinispan muszą być skonfigurowane z atrybutem remoteStore, aby dane mogły być przechowywane w zdalnych (w innym centrum danych, przyp. tłumacza) pamięciach podręcznych. Istnieją oddzielne klastry Infinispan wśród serwerów JDG, więc dane przechowywane na JDG1 w lokalizacji site1 zostaną zreplikowane na JDG2 w lokalizacji site2.
A na koniec, serwer odbierający JDG powiadamia serwery Keycloak swojego klastra za pomocą połączeń klienckich, co jest cechą protokołu HotRod. Węzły Keycloak na site2 aktualizują swoje pamięci podręczne Infinispan, a konkretna sesja użytkownika staje się również dostępna na węzłach Keycloak na site2.
Dla niektórych pamięci podręcznych możliwe jest również nie tworzenie kopii zapasowych i całkowite rezygnowanie z zapisu danych przez serwer Infinispan. W tym celu należy usunąć ustawienie remote-store z konkretnej pamięci podręcznej Infinispan (w pliku standalone-ha.xml), po czym konkretna replicated-cache również przestaje być potrzebna po stronie serwera Infinispan.
Konfiguracja pamięci podręcznych
Istnieją dwa typy pamięci podręcznych w Keycloak:
Lokalna. Znajduje się blisko bazy danych, służy do zmniejszenia obciążenia bazy danych oraz do obniżenia opóźnienia odpowiedzi. W tym typie pamięci podręcznej przechowywane są realm, klienci, role oraz metadane użytkowników. Ten typ pamięci podręcznej nie jest replikowany, nawet jeśli ta pamięć podręczna jest częścią klastra Keycloak. Jeśli zapis w pamięci podręcznej ulegnie zmianie, pozostałym serwerom w klastrze wysyłana jest wiadomość o zmianie, po czym zapis zostaje usunięty z pamięci podręcznej. Zobacz opis
workdalej, aby uzyskać szczegółowy opis procedury.Replikowany. Obsługuje sesje użytkowników, tokeny offline oraz monitoruje błędy logowania w celu wykrywania prób phishingowych i innych ataków. Przechowywane dane w tych pamięciach podręcznych są tymczasowe, przechowywane tylko w pamięci operacyjnej, ale mogą być replikowane w klastrze.
Pamięci podręczne Infinispan
Sesje — koncepcja w Keycloak, oddzielne pamięci podręczne, które nazywają się authenticationSessions, są używane do przechowywania danych konkretnych użytkowników. Żądania z tych pamięci podręcznych są zwykle potrzebne przeglądarkom i serwerom Keycloak, a nie aplikacjom. Tutaj ujawnia się zależność od sesji sticky, a takie pamięci podręczne nie muszą być replikowane, nawet w przypadku trybu Active-Active.
Tokeny operacji. Kolejna koncepcja, zwykle stosowana w różnych scenariuszach, kiedy na przykład użytkownik musi wykonać coś asynchronicznie poprzez e-mail. Na przykład podczas procedury zapomnij hasło cache actionTokens jest stosowany do śledzenia metadanych powiązanych z tokenami — na przykład token został już użyty i nie może być aktywowany ponownie. Tego typu pamięć podręczna musi być zazwyczaj replikowana między centrami danych.
Buforowanie i wygasanie przechowywanych danych działa, aby odciążyć bazę danych. Takie buforowanie poprawia wydajność, ale stwarza oczywisty problem. Jeśli jeden serwer Keycloak aktualizuje dane, pozostałe serwery muszą zostać o tym powiadomione, aby mogły przeprowadzić aktualizację danych w swoich pamięciach podręcznych. Keycloak korzysta z lokalnych pamięci podręcznych realms, users i autoryzacja do buforowania danych z bazy.
Istnieje również osobna pamięć podręczna work, która jest replikowana we wszystkich centrach danych. Sama nie przechowuje żadnych danych z bazy, a służy do wysyłania komunikatów o wygasaniu danych do węzłów klastra między centrami danych. Innymi słowy, gdy tylko dane są aktualizowane, węzeł Keycloak wysyła wiadomość do innych węzłów w swoim centrum danych oraz do węzłów w innych centrach danych. Po otrzymaniu takiej wiadomości każdy węzeł przeprowadza czyszczenie odpowiednich danych w swoich lokalnych pamięciach podręcznych.
Sesje użytkowników. Pamięci podręczne o nazwach sessions, clientSessions, offlineSessions i offlineClientSessions, zazwyczaj są replikowane między centrami danych i służą do przechowywania danych o sesjach użytkowników, które są aktywne podczas aktywności użytkownika w przeglądarce. Te pamięci podręczne współpracują z aplikacją przetwarzającą żądania HTTP od końcowych użytkowników, więc są związane ze sticky sessions i muszą być replikowane między centrami danych.
Ochrona przed atakiem brute force. Pamięć podręczna loginFailures służy do śledzenia danych błędów logowania, na przykład ile razy użytkownik wprowadził nieprawidłowe hasło. Replikacja tej pamięci podręcznej jest sprawą administratora. Jednak dla dokładnego liczenia warto aktywować replikację między centrami danych. Z drugiej strony, jeśli nie replikować tych danych, można poprawić wydajność, a jeśli staje to na przeszkodzie — replikację można pominąć.
Podczas rozkładania klastra Infinispan należy dodać definicje pamięci podręcznych do pliku konfiguracyjnego:
Należy skonfigurować i uruchomić klaster Infinispan przed uruchomieniem klastra Keycloak
Następnie należy skonfigurować remoteStore cache dla Keycloak. Aby to zrobić, wystarczy skrypt, który jest tworzony analogicznie do poprzedniego, używanego do konfiguracji zmiennej CACHE_OWNERS, należy go zapisać w pliku i umieścić w katalogu /opt/jboss/startup-scripts:
Zawartość skryptu
embed-server --server-config=standalone-ha.xml --std-out=echo
batch
echo *** Aktualizuj podsystem infinispan ***
/subsystem=infinispan/cache-container=keycloak:write-attribute(name=module, value=org.keycloak.keycloak-model-infinispan)
echo ** Dodaj zdalne powiązanie gniazd do serwera 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 ** Aktualizuj element roboczy dla replicated-cache **
/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 ** Aktualizuj element sesji dla distributed-cache **
/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 ** Aktualizuj element offlineSessions dla distributed-cache **
/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 ** Aktualizuj element clientSessions dla distributed-cache **
/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 ** Aktualizuj element offlineClientSessions dla distributed-cache **
/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 ** Aktualizuj element loginFailures dla distributed-cache **
/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 ** Aktualizuj element actionTokens dla distributed-cache **
/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 ** Aktualizuj element authenticationSessions dla distributed-cache **
/subsystem=infinispan/cache-container=keycloak/distributed-cache=authenticationSessions:write-attribute(name=statistics-enabled,value=true)
echo *** Aktualizuj podsystem undertow ***
/subsystem=undertow/server=default-server/http-listener=default:write-attribute(name=proxy-address-forwarding,value=true)
run-batch
stop-embedded-serverNie zapomnij ustawić JAVA_OPTS dla węzłów Keycloak do działania HotRod: remote.cache.host, remote.cache.port i nazwa usługi jboss.site.name.
Linki i dodatkowa dokumentacja
Artykuł został przetłumaczony i przygotowany dla Habr przez pracowników — intensywy, kursy wideo i szkolenia korporacyjne prowadzone przez praktyków (Kubernetes, DevOps, Docker, Ansible, Ceph, SRE)
Źródło: habr.com
