Keycloak
Siehe auch
Apache: mod_auth_openidc
oauth
OAuth2-Proxy: https://github.com/oauth2-proxy/oauth2-proxy, https://oauth2-proxy.github.io/oauth2-proxy/docs/
- Ansible-Rolle 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):
Der Client (genauer: ein OAuth-Client) leitet den Benutzer auf Keylcoak um.
Der Benutzer authentifiziert sich mit seinen Keycloak-Credentials. Keycloak generiert eine neue Session-ID, die als Cookie im Browser des Benutzers gespeichert wird.
Der Benutzer wird mit einem Token auf den Client umgeleitet.
Der Client bekommt einen Access-Token zugewiesen.
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.
<h1>MyApp</h1>
<p>
<?php
echo 'Welcome, ' . $_SERVER['OIDC_CLAIM_email'];
echo '<pre>';
print_r($_SERVER);
echo '</pre>';
?>
</p>
- Neuen Realm hinzufügen:
- Client (zu schützende Applikation) einrichten:
- Authentication Policies für die Benutzer festlegen:
- Benutzer anlegen (sofern sie nicht aus einem LDAP stammen):
- Event-Logging konfigurieren:
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