Keycloak. Bezpieczeństwo w świecie mikroserwisów
Wraz z zaletami wdrażania architektury mikroserwisowej, pojawiają się również nowe wyzwania — zwłaszcza w zakresie bezpieczeństwa. W przypadku mikroserwisów, gdzie wiele autonomicznych usług musi ze sobą współpracować, odpowiednie zabezpieczenie każdego z komponentów staje się priorytetem. W artykule skupimy się na roli Keycloak w zabezpieczaniu architektury mikroserwisowej oraz przeanalizujemy kluczowe elementy konfiguracyjne. Zobaczysz, jak można definiować role, zakresy i warunki, aby skutecznie kontrolować dostęp do poszczególnych zasobów. Omówimy również pewne wyzwania związane z implementacją narzędzia.
Architektura mikroserwisowa a bezpieczeństwo
Architektura mikroserwisowa posiada wiele zalet, wśród których wymienia się naturalny podział systemu na autonomiczne komponenty odpowiedzialne za konkretną część domeny biznesowej, skalowalność oraz niezależność wdrożeniową.
W przypadku podziału systemu na wiele komponentów należy zadbać o odpowiednie zabezpieczenie każdego z nich, co może okazać się większym wyzwaniem niż zabezpieczenie jednej monolitycznej aplikacji.
Weryfikacja użytkownika
Ważną cechą mikroserwisów jest zorientowanie na usługi, które pozwalają na ukrycie złożoności systemu i reprezentację akcji biznesowych poprzez wysokopoziomowe API. Takie podejście do architektury wymaga odpowiedniego zabezpieczenia każdej z usług. Należy zapewnić, że każda usługa zostanie wykorzystana tylko przez użytkownika, który posiada do niej uprawnienia. Domyślnie dostęp do wszystkich usług musi być zablokowany — może on zostać uzyskany jedynie po poprawnej weryfikacji użytkownika przez system.
Weryfikacja użytkownika sprowadza się do uzyskania odpowiedzi na dwa poniższe pytania:
Czy użytkownik jest tym, za kogo się podaje? – Uwierzytelnienie
Czy użytkownik ma uprawnienia do danej części systemu? – Autoryzacja
Keycloak
Keycloak jest narzędziem zgodnym ze standardami OAuth2 i OpenId Connect, które wykorzystuje tokeny JWT w celu implementacji uwierzytelnienia oraz autoryzacji. Działa on jako niezależny mikroserwis, który z łatwością może zostać wkomponowany w istniejącą architekturę systemu, komunikując się z jego elementami za pomocą protokołu HTTP.
Keycloak może zostać skonfigurowany w celu dostosowania do konkretnych potrzeb za pomocą panelu administratora lub bezpośrednio poprzez API. Wykorzystanie API do konfiguracji jest przydatne szczególnie w celu automatyzacji powtarzalnych jej elementów.
Specyfikacja Keycloak wyróżnia najważniejsze obiekty, które pozwalają na konfigurację procesu uwierzytelnienia i autoryzacji:
Client
Pracę z Keycloak zaczynamy od stworzenia klienta. Jest to obiekt reprezentujący aplikacje, które w imieniu użytkownika będą integrowały się z Keycloak w celu weryfikacji jego tożsamości oraz uprawnień. W szczególności z wykorzystaniem klienta inicjowany będzie proces logowania użytkownika do systemu. W architekturze mikroserwisowej — zgodnie z zasadą pojedynczej odpowiedzialności — przypiszemy każdemu mikroserwisowi oraz aplikacji frontendowej unikalnego klienta.
Należy zwrócić szczególną uwagę na poprawną konfigurację klienta. Wyróżnia się jego trzy rodzaje:
- Klient typu public, wykorzystywany jest w przypadku, gdy aplikacja z nim powiązana nie jest w stanie w bezpieczny sposób przechowywać kluczy bezpieczeństwa (ang. secret). Wpływa to na możliwe do wykorzystania sposoby uzyskania tokenu. Wykorzystywany do inicjalizacji procesu logowania użytkownika do systemu z wykorzystaniem aplikacji typu SPA oraz aplikacji mobilnych.
- Klient typu confidential, wykorzystywany jest w przypadku, gdy aplikacja z nim powiązana jest w stanie w bezpieczny sposób przechowywać klucz bezpieczeństwa, z którego wykorzystaniem może uzyskać token. Najczęściej jest to aplikacja działająca po stronie serwera.
- Klient typu bearer-only, wykorzystywany jest przez aplikacje, które nie inicjalizują procesu uwierzytelnienia — nie są wykorzystywane do uzyskania tokenu. Są to zwykle mikroserwisy, które otrzymują wygenerowany wcześniej token, walidują go i przekazują dalej.
Kolejnym istotnym elementem konfiguracji klienta jest wybór możliwych sposobów, w jaki ten może uzyskać token JWT, a więc uwierzytelnić użytkownika. Są one zgodne ze standardem OAuth2 (Grant Types). Najważniejsze z nich to:
Standard Flow — Sposób uwierzytelnienia, który opiera się na przekierowanie przeglądarki użytkownika do Keycloak. Jego odpowiednikiem w standardzie OAuth2 jest Authorization Code Flow, dla zwiększenia bezpieczeństwa często wykorzystywany z rozszerzeniem o PKCE.
Service Account Roles — Sposób uwierzytelnienia opierający się na wygenerowaniu tokenu JWT z wykorzystywaniem klucza bezpieczeństwa przypisanego do klienta. Nie wymaga udziału użytkownika i jest głównie stosowany w sytuacjach, gdy dwie aplikacje serwerowe muszą się wzajemnie zintegrować (reakcje na zdarzenia systemowe, procesy uruchamiane cyklicznie). Jego odpowiednikiem w standardzie OAtuh2 jest Client Credentials Grant.
Poniższe obiekty zawsze definiujemy w konkretnym kliencie, którego rolą jest przechowywanie informacji o uprawnieniach użytkowników (należy pamiętać o włączeniu możliwości Autoryzacji w ustawieniach klienta).
Resource
Keycloak wprowadza pojęcie Zasobu (ang. Resource) oraz Zakresu (ang. Scope). Każda usługa mikroserwisu reprezentuje wykonanie pewnej akcji biznesowej na zasobach w określonym Scope. Dzięki wprowadzeniu pojęcia Scope możemy zdefiniować wiele akcji dotyczących tego samego zasobu (obiektu domenowego), co jest odzwierciedleniem tego, jak projektujemy nasze systemy.
Zwykle mamy wiele akcji biznesowych powiązanych z jednym obiektem domenowym. Zatem dzięki podzieleniu naszej domeny biznesowej na zasoby, możemy potem niezależnie decydować o uprawnieniach do wykonywania akcji na nich. Zwykle zasoby reprezentowane są przez rzeczowniki reprezentujące obiekty z naszej domeny biznesowej. Obiekty typu Scope są najczęściej określane za pomocą czasowników, którymi definiujemy akcje wykonywane w domenie biznesowej.
Przykład, w jaki sposób można przypisać pary Resource-Scope do Usług:
W celu stworzenia par Resource-Scope można posłużyć się panelem administratora lub API:
Dzięki odpowiedniej konfiguracji zasobów w dalszych krokach będziemy mogli zdefiniować warunki, które musi spełnić użytkownik, aby uzyskać do nich dostęp.
Policy
Definiuje warunki, na podstawie których Keycloak podejmuje decyzję o przyznaniu bądź zablokowaniu dostępu użytkownika do danego zasobu. Zauważmy, że obiekt Policy definiuje jedynie warunek i nie jest jawnie powiązany z konkretnym zasobem. Najważniejsze rodzaje warunków, które możemy zdefiniować to:
Role Based Policy — Definiuje warunek oparty na roli:
Użytkownik ma dostęp do obiektu, jeśli jest w roli ADMIN.
Client Based Policy — Definiuje warunek oparty na definicji klienta:
Użytkownik ma dostęp do obiektu, jeśli jego token dostępu został wygenerowany za pomocą demo-backend-client.
User Based Policy — Definiuje warunek operaty na konkretnym użytkowniku:
Użytkownik ma dostęp do obiektu, jeśli jego login to exampleUser.
Aggregated Policy — Pozwala na tworzenie złożonych warunków opierających się na innych obiektach typu Policy:
Użytkownik ma dostęp do obiektu, jeśli spełnia jedną z przypisanych Polityk.
W sytuacji, gdy utworzyliśmy zasoby oraz polityki możemy je ze sobą powiązać, aby zdecydować, jakie warunki musi spełnić użytkownik w celu uzyskania uprawnień do zasobów. Kolejno dzięki powiązaniu zasobów z usługami, otrzymujemy system, w którym użytkownik musi spełnić konkretne warunki, aby uzyskać dostęp do wybranych usług.
Permission
Permission wiąże pary Resource-Scope z warunkami zdefiniowanymi za pomocą Policy, które są sprawdzane w trakcie weryfikacji uprawnień użytkownika do danego obiektu:
Obiekt Permission związany z zasobem USER w scope CREATE oraz obiektami Policies:
- Admin Role Policy
- Demo Backend Client Policy
- Example User Policy
Permission jest tak skonfigurowany, aby udzielać dostępu do określonych w nim zasobów, gdy przynajmniej jeden z warunków zdefiniowanych w obiektach Policy jest spełniony (Decision strategy).
Implementacja uwierzytelnienia z wykorzystaniem Keycloak
Po skonfigurowaniu użytkowników wraz z uprawnieniami możemy wykorzystać Keycloak do zalogowania się użytkownika do systemu.
Przeanalizujmy proces uwierzytelniania z wykorzystaniem Authorization Code Flow:
W przypadku, gdy użytkownik nie jest zalogowany, aplikacja frontendowa przekierowuje go do ekranu logowania Keycloak, gdzie po podaniu poprawnego loginu i hasła otrzymuje token JWT i uzyskuje dostęp do systemu. Jego tożsamość będzie potwierdzana przez komponenty systemu z wykorzystaniem otrzymanego tokenu. Jest on przesyłany z każdym żądaniem HTTP i walidowany przez mikroserwisy. Jeśli jest niepoprawny usługa mikroserwisu zwróci HTTP Status Unauthorized – 401.
Token JWT składa się z trzech zakodowanych części:
- Header — zawiera metadane określające rodzaj tokenu oraz nazwę algorytmu, za pomocą którego został podpisany.
- Payload — zawiera dane o użytkowniku, któremu token został wydany takie jak jego login oraz nadane mu role. Przechowuje również użyteczne informacje na temat samego tokenu.
- Signature — wygenerowana za pomocą zakodowania dwóch pierwszych części JWT za pomocą klucza, który nie jest publicznie dostępny. Jest wykorzystywana do weryfikacji poprawności tokenu oraz iż token nie został zmieniony w trakcie transmisji.
Implementacja autoryzacji z wykorzystaniem Keycloak
Po poprawnym uwierzytelnieniu użytkownika system musi podjąć decyzję o udzieleniu bądź zablokowaniu dostępu do określonej usługi powiązanej z daną parą Resource-Scope.
Przeanalizujmy proces autoryzacji na przykładzie usługi do tworzenia kont użytkownika.
Administrator poprawnie zalogował się do systemu (uzyskał token JWT) i następnie próbuje uzyskać dostęp do usługi tworzenia użytkownika. Usługa ta została skonfigurowana tak, aby weryfikować uprawnienie do zasobu USER i scope CREATE.
Po poprawnej walidacji JWT User Service generuje token RPT (ang. Requesting Party Token) w celu pobrania uprawnień użytkownika. Jest to token JWT wzbogacony o informacje na temat uprawnień, które znajdują się w jego polu o nazwie permissions.
{
"authorization" : {
"permissions" : [
{
"rsid" : "bd1b652e-e5e2-4aa9-9df0-e4c24b831408",
"rsname" : "USER"
}
{
"rsid" : "59a64ae1-4406-4752-ac73-2f8273b035d6",
"rsname" : "Default Resource"
}
]
}
}
Widać, że użytkownik ma uprawnienia do zasobu USER w scope CREATE, zatem każda usługa, która weryfikuje to uprawnienie umożliwi użytkownikowi wywołanie jej. W przypadku braku uprawnienia, usługa zwróci HTTP Status 403 – Forbidden.
Uprawnienie do USER w scope CREATE znalazło się w tokenie RPT użytkownika, ponieważ został spełniony jeden z warunków zdefiniowany w obiektach Policy przypiętych do obiektu Permission powiązanego z tym zasobem USER w scope CREATE.
Wyzwania związane z implementacją Keycloak
Należy pamiętać o kilku aspektach, aby poprawnie wykorzystywać narzędzie, którym jest Keycloak w architekturze mikroserwisowej.
Wydajność
Usługi Keycloak są intensywnie wykorzystywane przez komponenty systemu w celu generowania tokenów oraz walidacji ich. Aby zredukować opóźnienia do minimum należy zadbać o implementację cache (wraz z rozsądnym czasem odświeżania) oraz efektywną walidację tokenów.
Zabezpieczenie funkcji administracyjnych
Keycloak posiada panel administratora oraz API administratora. Są one wykorzystywane do konfiguracji Keycloak’a w tym konfiguracji modelu uprawnień. Należy zabezpieczyć funkcje administratora przed nieautoryzowanym dostępem.
Wysoka dostępność
Keycloak jest narzędziem wykorzystywanym przez wszystkie komponenty systemu. W sytuacji jego niedostępności system nie będzie mógł zweryfikować tożsamości oraz uprawnień użytkowników co oznacza de facto niedostępność całego systemu. Należy zatem zadbać o wysoką dostępność Keycloak.
Rozmiar nagłówków HTTP
Tokeny są często przekazywane z wykorzystaniem nagłówków HTTP. Należy rozsądnie podejść do ilości informacji zapisywanych w tokenie. W przypadku zbyt dużego rozmiaru nagłówka, żądanie HTTP może zostać zablokowane w trakcie jego transportu.
Podsumowanie
Keycloak jest świetnym narzędziem, które znacząco przyspiesza proces implementacji uwierzytelnienia oraz autoryzacji. Dzięki API, które udostępnia pozwala na wpięcie w istniejącą strukturę mikroserwisów oraz implementacje złożonych rozwiązań z zakresu bezpieczeństwa.