Keycloak

Keycloak ist eine Open Source IAM-Lösung:

  • Identity Management: Provisionierung, Administration und Deprovisionierung von Benutzern

  • Access Management: Authentication (AuthN: Identifikation) und Authorization (AuthZ: Berechtigungen)

Wer in Applikationen keine Benutzerschicht implementieren möchte, nutzt Keycloak. Keycloak bietet

  • Benutzer- und Gruppenmanagement

  • Session Management

  • Brute Force Detection

  • Passwort-Policies, Verify email, …

  • User Registration

  • User Self Service (Forgot Password, Edit User Data etc.)

  • User Consent

  • 2-Factor-Auth (z.B. mit OTP)

  • Identity Federation/Brokering (OpenID Connect, also OAuth 2.0 plus Login- und Profil-Informationen, oder SAML 2.0): Verbindung zu externen Identity Providern, die SAML oder OIDC unterstützen, wie Google, GitHub, Twitter, Facebook, …

  • User Storage Federation über Kerberos (AD) oder LDAP

  • Templating (Layout, E-Mails etc.)

Links

Container

Keycloak als Container im Developer Mode starten:

podman run \
    --detach \
    --name=keycloak \
    --publish=8080:8080 \
    --env=KEYCLOAK_ADMIN=admin \
    --env=KEYCLOAK_ADMIN_PASSWORD=linuxfabrik \
    quay.io/keycloak/keycloak:latest start-dev

Benutzer

Besonders interessant für die Konfiguration:

  • Authentication > Policies

  • Authentication > Required Actions

  • Users > Credentials > Reset Password

  • Users > Role Mappings

  • Users > Details: Required User Actions

  • Realm settings > Login

MariaDB/PostgreSQL statt H2: Eine SQL-Datenbank wie MariaDB oder PostgreSQL ist nur bei grösseren Installationen oder bei einem Keycloak-Cluster (dann zwingend und dediziert) nötig, ansonsten verwendet Keycloak intern die Java SQL H2-Datenbank. In diesem Fall liegen die DB-Dateien in standalone/data/. Die H2-DB lässt sich per java -jar modules/system/layers/base/com/h2database/h2/main/h2-*.jar administrieren (auf der lokalen Admin-Maschine per sshfs ausführen - es wird ein Browser gestartet).

Damit eine benutzerdefinierte Datenbank angebunden werden kann, ist die Entwicklung eines User Storage SPI-Plugins notwendig (Service Provider Interfave). Das Plugin kommuniziert zwischen Keycloak und der benutzerdefinierten Datenbank, und kümmert sich um die Abfrage und das Updaten der Benutzerdaten, Authentication und Access Control. Diese Plugins werden in Java geschrieben.

Clients

Mit „Client“ ist immer die Applikation gemeint, nicht die Benutzer.

Im Fall von OpenID Connect/OAuth wird die Applikation durch einen OAuth-Client geschützt, indem dieser bei nicht vorhandener Session den Keycloak-Server um Authentifizierung bittet. Der Authentication Flow ist dabei wie folgt (und wird in openid-saml genauer beschrieben):

  1. Der Client (genauer: ein OAuth-Client) leitet den Benutzer auf Keylcoak um.

  2. Der Benutzer authentifiziert sich mit seinen Keycloak-Credentials. Keycloak generiert eine neue Session-ID, die als Cookie im Browser des Benutzers gespeichert wird.

  3. Der Benutzer wird mit einem Token auf den Client umgeleitet.

  4. Der Client bekommt einen Access-Token zugewiesen.

  5. Mit Hilfe des Access-Tokens kann der Client Zugriff gewähren.

Der Client kann selbst noch eigene Cookies anlegen, um den Status seines Benutzers zu speichern. Die Client-Session und die Keycloak-Session sind also immer voneinander getrennt, der Authentication-State ist aber über Keycloak und alle Clients identisch, wobei der Authentication-State immer auf dem Keycloak-Server gespeichert ist. So wird SSO möglich.

Entweder bringt der Client einen OAuth-Client/die passende Library mit (wie z.B. Grafana), man nutzt einen speziellen OAuth-Proxy (z.B. oauth2-proxy), oder man installiert einen im Webserver (z.B. für Apache mod_auth_openidc). Die verschiedenen Arten in der Übersicht:

              :443            :443         :443        :443
           oauth2-proxy    AnyRevProxy    Apache    AnyRevProxy
                |               |        mod_auth        |
                |               |            |           |
                |             :4180          |       Web-App +
                |          oauth2-proxy      |      OAuth-Client
                |               |            |           |
Keycloak <------+---------------+------------+-----------+
                |               |            |
             Web-App         Web-App      Web-App

SSO

Integrationsmethoden/Protokolle für SSO:

  • OpenID Connect (OIDC)

  • SAML 2.0

Bei Applikationen, die lokal beim Nutzer im Source Code laufen, können Authorization Code, die Client-ID und der das Client-Secret nicht hinterlegt werden. Daher unterscheidet Keycloak zwei Zugriffstypen für Applikationen:

  • Confidential (in Keycloak „Client authentication“ auf „on“): Für traditionelle Webapplikationen, deren Source Code auf dem Server gespeichert ist.

  • Public (in Keycloak „Client authentication“ auf „off“): Für Applikationen, deren Source Code lokal einsehbar ist. Hier wird PKCE (Proof-Key for Code Exchange, sprich „pixi“) verwendet, um sich gegenüber Keycloak zu authentifizieren. Von PKCE können auch Confidential-Applikationen profitieren, da damit eine zusätzliche Authentifizierungsmassnahme umgsetzt wird. OAuth 2.1 schreibt daher die Nutzung von PKCE vor.

Applikationstypen, die per Keycloak SSO-fähig gemacht werden können:

  • Traditionelle Webapplikationen, die auf dem Server gespeichert und abgearbeitet werden. Hier wird mit Hilfe der Redirects auf Basis von OIDC oder SAML gearbeitet.

  • Single Web Page Applikationen (SPA) auf Basis eines Javascript-Frontends wie Angular oder React, die komplett im Browser im Source Code ablaufen. Es kommt OIDC mit PKCE zum Einsatz. Zudem ist der Ablauf, um ein Access Token zu erhalten, erweitert:

    • Die SPA erzeugt einen zufälligen Wert. Dieser wird als hashed Value zusammen mit dem Redirect-Request als „code_verifier“ an Keycloak gesendet.

    • Die SPA verwendet den Autz-Code plus code_verifier, um ein Access Token zu erhalten (siehe openid-saml). Damit wird die SPA als Inhaber des Authz-Codes indentifiziert.

  • Native Applications, z.B. Mobile Applications. Diese sind ähnlich wie SPA zu konfigurieren. Die Native Application muss sich bei der Authentifizierung allerdings auf einen Browser verlassen.

SAML unterstützt nur traditionelle Webapplikationen; bei SPA und Native Applications muss OIDC verwendet werden.

Logout:

  • Aus Sicht des Clients: Der Client erhält vom Keycloak-Server in der Authentication Response den Parameter session_state. Der enthaltene String-Value zeigt an, ob sich der Benutzer von Keycloak abgemeldet hat. Der Client sendet daher periodische Anfragen an den Keycloak-Server, um den Session-State zu ermitteln. Wurde der Benutzer ausgeloggt, sendet der Client den Benutzer zurück zur Keycloak Login-Seite.

  • Aus Keycloak-Sicht: Kennt die SSO-Sessions für jeden Benutzer (Realm > Sessions, Realm > Realm settings > Sessions), als auch die Tokens für die Clients (Realm > Realm settings > Tokens).

URLs und Ports

Wichtige Keycloak-URLs - System:

  • User Account Console: /auth/realms/$REALM/account

Wichtige Keycloak-URLs - OpenID:

  • OpenID-Config: /realms/$REALM/.well-known/openid-configuration

  • Authorization-URL (Login): /auth/realms/$REALM/protocol/openid-connect/auth

  • Token-URL (Redeem): /auth/realms/$REALM/protocol/openid-connect/token

  • Introspection-URL: /auth/realms/$REALM/protocol/openid-connect/token/introspect

  • UserInfo-URL (Profile): /auth/realms/$REALM/protocol/openid-connect/userinfo

  • End-Session-URL: /auth/realms/$REALM/protocol/openid-connect/logout

  • JWKS-URI: /auth/realms/$REALM/protocol/openid-connect/certs

  • Check-Session-iFrame: /auth/realms/$REALM/protocol/openid-connect/login-status-iframe.html

Ports:

  • 8080/tcp

  • Admin-Console: 9990/tcp

User Federation

Externe IdPs

Stichwort: Identity Brokering, Federated Authentication

Benutzer können in externen und/oder Unternehmens-IdPs gespeichert und von Keycloak verwendet werden. Anfragen an Keycloak werden nach der Konfiguration eines Trust Relationship von diesem per Redirect an den externen IdP weitergeleitet. Der Ablauf:

  • Alice ruft eine Webseite auf, die eine Trust Relationship zu Keycloak als Identity Provider konfiguriert hat.

  • Alice gehört aber zu einer Firma, die ihren eigenen IdP verwendet, und die die Benutzerinformationen dort verwaltet.

  • Beim Aufruf sendet die Webseite Alices Authentication Request an Keycloak weiter, der ihre Credentials nicht kennt.

  • Keycloak leitet an Alices Firmen-IdP (den „Federated Identity Provider“) einen Sign-in-Request weiter, um sie dort zu authentifizieren.

  • Damit das funktioniert, wurde vorher eine Trust Relationship zwischen Keycloak und dem externen IdP in Keycloak konfiguriert.

  • Alice loggt sich am externen IdP ein.

  • Der externe IdP sendet ein von ihm signiertes Token1 an Keycloak. Keycloak prüft die Signatur von Token1.

  • Keycloak leitet mit einem von Keycloak erstellten Token2 zur Webseite um. Die Webseite prüft die Signatur von Token2.

Token1 und Token2 haben nichts miteinander zu tun. Token1 beinhaltet die Claims, um Alice zu identifizieren, und Token2 die Claims, damit die Webseite die korrekten Authentisierungs-Informationen erhält. Für die Übersetzung werden „Mapping Claims“ (bei SAML: „Mapping Assertions“) konfiguriert. Claims wie beispielsweise „mail“ und „user“ in Token1 werden konkret in Claims wie „username“ und „email“ in Token2 übersetzt (gilt auch für Roles).

LDAP per FreeIPA

Benutzer aus dem FreeIPA-LDAP synchronisieren. Im passenden Realm auf User Federation > Add provider > LDAP klicken. Dann:

  • Console Display Name: FreeIPA LDAP

  • Edit Mode: WRITABLE (falls die Benutzer ihr Passwort o.ä. per Keycloak ändern dürfen)

  • Vendor: Red Hat Directory Server

    • Username LDAP attribute: uid

    • RDN LDAP attribute: uid

    • UUID LDAP attribute: nsuniqueid

    • User Object Classes: inetOrgPerson, organizationalPerson

  • Connection URL: ldap://freeipa:389

  • Users DN: cn=users,cn=accounts,dc=linuxfabrik,dc=it

  • Bind Type: simple

  • Bind DN: cn=Directory Manager

Im Abschnitt „Sync Settings“ lässt sich die Art (Full vs. Partial) sowie die Häufigkeit der Synchronisation konfigurieren.

Wer LDAP-Gruppenzugehörigkeit auf Roles in Keycloak mappen möchte, erstellt unter „User Federation > LDAP > Mappers“ einen „role-ldap-mapper“, z.B. „mygroup-role-ldap-mapper“:

  • Name: mygroup-role-ldap-mapper

  • Mapper Type: role-ldap-mapper

  • LDAP Roles DN: cn=groups,cn=accounts,dc=linuxfabrik,dc=it (dort finden sich unter „member“ die zur Gruppe gehörenden Benutzer)

  • LDAP Filter: (cn=mygroup)

  • Nach dem Klick auf „Sync LDAP Groups To Keycloak“ taucht die gewünschte Gruppe im Keycloak-Realm in der obersten Ebene unter „Groups“ auf.

Realms

Realm einrichten

Ein Realm ist eine logische Gruppierung von

  • Benutzern

  • Clients (also Applikationen wie beispielsweise Nextcloud, Grafana usw.)

  • Rollen

  • UI-Themes

  • Identity Providers (SAML / OIDC)

  • User Storage Federations (LDAP / Kerberos / Custom DB)

Die Screenshots zeigen die Einrichtung eines Realms „example“ für eine zu schützende PHP-Applikation ohne eigene Authentifizierungs-Mechanismen (Client), die sich auf den OAuth-Client im Apache und Keycloak verlässt, und anschliessend die von Keycloak gelieferten Daten ausliest. In den Screenshots wird dem Realm die UUID „3323f2c6-5389-4bc3-9a25-a8d8ae64c6d4“ zugewiesen, die so auch in der Webserver/Reverse Proxy Konfiguration verwendet werden muss.

test.php
<h1>MyApp</h1>
<p>
    <?php
        echo 'Welcome, ' . $_SERVER['OIDC_CLAIM_email'];
        echo '<pre>';
        print_r($_SERVER);
        echo '</pre>';
    ?>
</p>
Neuen Realm hinzufügen:
../_images/keycloak-realm-01.png
../_images/keycloak-realm-02.png
../_images/keycloak-realm-03.png
../_images/keycloak-realm-04.png
Client (zu schützende Applikation) einrichten:
../_images/keycloak-realm-05.png

Der Tab „Credentials“ erscheint nur, wenn „Access Type: confidential“ gewählt wurde. Bedeutet, dass der OAuth-Client eine Client-ID und ein Secret verwenden muss.

../_images/keycloak-realm-06.png

„Secret“ notieren, wird für die Einrichtung der Webserver-vHosts benötigt.

Authentication Policies für die Benutzer festlegen:
../_images/keycloak-realm-07.png
../_images/keycloak-realm-08.png
../_images/keycloak-realm-09.png
Benutzer anlegen (sofern sie nicht aus einem LDAP stammen):
../_images/keycloak-realm-10.png
../_images/keycloak-realm-11.png
Event-Logging konfigurieren:
../_images/keycloak-realm-12.png

Testen eines Realms

Was liefert Keycloak auf Anfragen zurück? Kann auf jedem beliebigen Host ausgeführt werden:

# Setttings
KEYCLOAK_HOST=https://idp.linuxfabrik.ch
KEYCLOAK_USERNAME=<Keycloak username>
KEYCLOAK_PASSWORD=<Keycloak password>
KEYCLOAK_REALM=<Keycloak realm name>
KEYCLOAK_CLIENT_SECRET=<Keycloak client secret>
KEYCLOAK_CLIENT_ID=<Client ID>

# Get token
TOKEN=$(curl --silent \
    --data="client_id=$KEYCLOAK_CLIENT_ID" \
    --data="client_secret=$KEYCLOAK_CLIENT_SECRET" \
    --data="username=$KEYCLOAK_USERNAME" \
    --data="password=$KEYCLOAK_PASSWORD" \
    --data="grant_type=password" \
    "$KEYCLOAK_HOST/auth/realms/$KEYCLOAK_REALM/protocol/openid-connect/token" | jq -r '.access_token')

# Use token to get userinfo
curl --silent \
    --header="Authorization: bearer $TOKEN" \
    $KEYCLOAK_HOST/auth/realms/$KEYCLOAK_REALM/protocol/openid-connect/userinfo | jq

2FA: One Time Passwords (OTP)

Konfiguriert sich ein Benutzer ein OTP (Nutzung per FreeOTP oder Google Authenticator) in auth/realms/$REALM/account/totp, codiert der QR-Code u.a.

  • TOTP (Time-based OTP)

  • Realm-Name

  • Benutzername

  • gemeinsames Secret: 32 stellig, alphanumerisch, wechselt bei jedem Seiten-Reload

  • OTP-Länge: genau 6 Zeichen

  • Algorithmus: SHA1

  • period - Wechsel des OTP nach: 30 Sekunden

Diese Default-Vorgaben lassen sich in Authentication > OTP Policy ändern. Unter Authentication > Required Actions > Configure OTP lässt sich einstellen, dass alle neuen Benutzer ein OTP verwenden müssen.

Hinweis: die Google Authenticator App auf Android hat Stand 2020-02 mit 8 statt 6 Zeichen und SHA256 statt SHA1 ein Problem; auf dem iPhone dagegen funktioniert der Google Authenticator tadellos. Mit FreeOTP klappt es dagegen immer und auf beiden Plattformen.

API

Keycloak ist eine „API-first“ Applikation, und kann daher vollautomatisiert werden.

Erzeugen eines Realms:

POST / auth/admin/realms/$REALM/clients HTTP/1.1
Host: $KEYCLOAK_HOST
Authorization: Bearer $ACCESS_TOKEN
Content-Type: application/json

{
    "clientId": "my-client"
    "enabled": true,
    "protocol": "openid-connect"
}

CLI

Client erstellen:

kcadm.sh create clients
    -r $REALM \
    -s clientId=$CLIENT_ID \
    -s enabled=true \
    -s protocol=$PROTOCOL

Troubleshooting

tail -f /opt/keycloak/standalone/log/server.log

Werden die richtigen Header gesendet, z.B. vom Reverse Proxy? Keycloak beenden und mit nc einen Service auf Port 8080 simulieren, damit man die eingehenden Anfragen an den vermeintlichen Keycloak-Server sieht und auswerten kann.

nc -l 8080

Ergibt z.B.:

GET /auth/realms/example/protocol/openid-connect/auth?response_type=code&scope=openid%20email&client_id=example&state=HcKXBTixVaDEPkm_64sfFr8lvTo&redirect_uri=https%3A%2F%2Fexample.linuxfabrik.ch%2F%2A&nonce=ZdCA4AEt9BmDvPEew_BGCvvcWNwfB1vli77embyRxkw HTTP/1.1
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng, */*;q=0.8
Connection: Keep-Alive
Cookie: AUTH_SESSION_ID=3d0208c1-80...6dd4821b36.myserver; KEYCLOAK_IDENTITY=eyJhbGc...-zYQ; KEYCLOAK_SESSION=example/cb49c45b-...f-460a3504ca02/3d0208c1-80...736dd4821b36
Host: 192.168.122.235:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.75 Safari/537.36
X-Forwarded-For: 192.168.26.1
X-Forwarded-Host: 192.168.122.235:8080
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: 192.168.122.235:8080
Failed to send email javax.mail.MessagingException: Could not convert socket to TLS; nested exception is: javax.net.ssl.SSLException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty

Als erstes versuchen, den Keycloak-Service neuzustarten, danach mit dem Debugging anfangen.

Setup Best Practises

Hostnames konfigurieren („Base URL“):

  • Frontend: Alle Keycloak-Nodes unter einer Public URL gruppieren („Frontend URL“ setzen)

  • Backend Endpoints zwischen Keycloak und Clients: Betrifft Token Endpoints, User info und JWKS (JSON Webkey Set). Müssen ebenfalls Public URLs sein, und können auf der Frontend URL basieren (ist der Default).

  • Administration: Die Admin-Konsole sollte nur intern zugreifbar sein.

Availability:

  • Keycloak-Instanzen als Cluster deployen. Keycloak nutzt Infinispan (analog Redis) als verteilte Cache-Instanz, um die Nodes im Cluster zu synchronisieren.

  • Keycloak sollte nur hinter einem ReverseProxy zugreifbar sein, der zusätzlich als Load Balancer agiert. Der ReverseProxy sollte TLS terminieren, und neu encrypten. Der ReverseProxy muss alle „X-Forwarded-*“ überschreiben und an Keycloak weiterleiten. Session Affinity (Sticky Sessions) konfigurieren.

TLS:

  • TLS in Keycloak aktivieren, per Zertifikat in PEM-Format, oder per Java Keystore. Die Kommunikation zwischen ReverseProxy und Keycloak sollte nur verschlüsselt erfolgen.

Datenbank:

  • Die interne H2-Datenbank sollte auf MariaDB, MySQL oder eine andere umgestellt werden.

  • Setting db-pool-max-size in Produktion nach oben anpassen (Default: 100, evtl. 150 oder mehr).

  • Setting db-pool-min-size auf einen Wert wie 10 setzen, da das Erzeugen von DB-Connections teuer ist.

Built on 2025-01-06