Collecter les journaux Keycloak
Ce document explique comment configurer Keycloak pour envoyer des journaux à Google Security Operations à l'aide de webhooks.
Keycloak est une solution Open Source de gestion des identités et des accès (IAM, Identity and Access Management) qui offre des fonctionnalités d'authentification unique (SSO, Single Sign-On), de fédération des utilisateurs, de courtage d'identité et de connexion via les réseaux sociaux. Il est compatible avec les protocoles OpenID Connect, OAuth 2.0 et SAML 2.0, et suit les événements utilisateur (connexion, déconnexion, inscription, modification de mot de passe) et les événements d'administration (opérations de gestion des utilisateurs, des clients, des royaumes et des rôles) pour l'audit de sécurité.
Avant de commencer
Assurez-vous de remplir les conditions préalables suivantes :
- Une instance Google SecOps
- Une instance Keycloak en cours d'exécution (version 20 ou ultérieure recommandée)
- Accès administrateur à la console d'administration Keycloak
- Accès au système de fichiers ou au conteneur du serveur Keycloak pour déployer des extensions
- Accès à la Google Cloud Console (pour créer une clé API)
Créer un flux de webhook dans Google SecOps
Créer le flux
- Accédez à Paramètres SIEM> Flux.
- Cliquez sur Add New Feed (Ajouter un flux).
- Sur la page suivante, cliquez sur Configurer un seul flux.
- Dans le champ Nom du flux, saisissez un nom pour le flux (par exemple,
Keycloak Events). - Sélectionnez Webhook comme type de source.
- Sélectionnez Keycloak comme type de journal.
- Cliquez sur Suivant.
- Spécifiez les valeurs des paramètres d'entrée suivants :
- Délimiteur de fractionnement (facultatif) : saisissez
\npour fractionner les événements multilignes (chaque POST de webhook contient un seul événement, vous pouvez donc laisser ce champ vide). - Espace de noms de l'élément : espace de noms de l'élément
- Libellés d'ingestion : libellé à appliquer aux événements de ce flux
- Délimiteur de fractionnement (facultatif) : saisissez
- Cliquez sur Suivant.
- Vérifiez la configuration de votre nouveau flux sur l'écran Finaliser, puis cliquez sur Envoyer.
Générer et enregistrer une clé secrète
Après avoir créé le flux, vous devez générer une clé secrète pour l'authentification :
- Sur la page d'informations sur le flux, cliquez sur Générer une clé secrète.
- Une boîte de dialogue affiche la clé secrète.
- Copiez et enregistrez la clé secrète de manière sécurisée.
Important : La clé secrète ne s'affiche qu'une seule fois et ne peut pas être récupérée ultérieurement. Si vous la perdez, vous devrez en générer une nouvelle.
Obtenir l'URL du point de terminaison du flux
- Accédez à l'onglet Détails du flux.
- Dans la section Endpoint Information (Informations sur le point de terminaison), copiez l'URL du point de terminaison du flux.
Le format d'URL est le suivant :
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreateou
https://<REGION>-malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreateEnregistrez cette URL pour les étapes suivantes.
Cliquez sur OK.
Créer une clé API Google Cloud
Chronicle nécessite une clé API pour l'authentification. Créez une clé API restreinte dans la Google Cloud Console.
Créer la clé API
- Accédez à la page Identifiants de la console Google Cloud.
- Sélectionnez votre projet (celui associé à votre instance Chronicle).
- Cliquez sur Créer des identifiants> Clé API.
- Une clé API est créée et affichée dans une boîte de dialogue.
- Cliquez sur Modifier la clé API pour la restreindre.
Restreindre la clé API
- Sur la page des paramètres Clé API :
- Nom : saisissez un nom descriptif (par exemple,
Chronicle Webhook API Key).
- Nom : saisissez un nom descriptif (par exemple,
- Sous Restrictions relatives aux API :
- Sélectionnez Restreindre la clé.
- Dans le menu déroulant Sélectionner des API, recherchez et sélectionnez API Google SecOps (ou API Chronicle).
- Cliquez sur Enregistrer.
- Copiez la valeur de la clé API depuis le champ Clé API en haut de la page.
- Enregistrez la clé API de manière sécurisée.
Activer le stockage des événements dans Keycloak
Avant de configurer l'extension de webhook, activez le stockage des événements dans Keycloak afin que les événements soient générés et disponibles pour le transfert.
Activer les événements utilisateur
- Connectez-vous à la console d'administration Keycloak.
- Sélectionnez le domaine que vous souhaitez surveiller dans le menu déroulant en haut à gauche.
- Accédez à Paramètres du domaine > Événements.
- Sélectionnez le sous-onglet Paramètres des événements utilisateur.
- Activez l'option Enregistrer des événements.
- Définissez la période d'expiration (minimum recommandé : 7 jours).
- Cliquez sur Enregistrer.
Activer les événements d'administration
- Dans l'onglet Événements, sélectionnez le sous-onglet Paramètres des événements d'administration.
- Activez l'option Enregistrer des événements.
- Activez l'option Inclure la représentation pour capturer tous les détails des objets modifiés.
- Définissez la période d'expiration (minimum recommandé : 7 jours).
- Cliquez sur Enregistrer.
Installer l'extension d'écouteur d'événements webhook
Keycloak n'inclut pas d'écouteur d'événements webhook natif. Installez l'extension keycloak-events depuis Phase Two (p2-inc) pour activer la diffusion des webhooks.
Télécharger et déployer l'extension
Téléchargez le dernier fichier JAR depuis la page des versions de keycloak-events sur Maven Central ou compilez-le à partir de la source :
git clone https://github.com/p2-inc/keycloak-events.git cd keycloak-events mvn clean installCopiez le fichier JAR complet obtenu dans le répertoire
providersde Keycloak :cp target/keycloak-events-*.jar /opt/keycloak/providers/Recompilez et redémarrez Keycloak :
/opt/keycloak/bin/kc.sh build /opt/keycloak/bin/kc.sh start
Activer l'écouteur d'événements webhook
- Connectez-vous à la console d'administration Keycloak.
- Sélectionnez le royaume cible dans le menu déroulant des royaumes.
- Accédez à Paramètres du domaine > Événements.
- Dans le menu déroulant Écouteurs d'événements, sélectionnez ext-event-webhook.
- Cliquez sur Enregistrer.
Configurer le webhook Keycloak
Créer l'URL de webhook
Combinez l'URL du point de terminaison Chronicle et la clé API :
<ENDPOINT_URL>?key=<API_KEY>Exemple :
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...
Créer un abonnement au webhook via l'API REST Keycloak
L'extension keycloak-events fournit des points de terminaison REST pour gérer les abonnements aux webhook. Utilisez l'API REST d'administration Keycloak pour créer un webhook.
Étape 1 : Obtenir un jeton d'accès
Demandez un jeton d'accès à Keycloak à l'aide d'un compte administrateur :
TOKEN=$(curl -sS -X POST "https://<KEYCLOAK_HOST>/realms/master/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=password" \ --data-urlencode "client_id=admin-cli" \ --data-urlencode "username=<ADMIN_USERNAME>" \ --data-urlencode "password=<ADMIN_PASSWORD>" \ | sed -n 's/.*"access_token":"\([^"]*\)".*/\1/p')
Remplacez les éléments suivants :
<KEYCLOAK_HOST>: nom d'hôte et port de votre serveur Keycloak (par exemple,keycloak.example.com:8443)<ADMIN_USERNAME>: votre nom d'utilisateur administrateur Keycloak<ADMIN_PASSWORD>: mot de passe administrateur Keycloak
Étape 2 : Créez le webhook
Envoyez une requête POST pour créer l'abonnement au webhook pour le domaine cible :
curl -sS -X POST "https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/webhooks" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Content-Type: application/json" \ -d '{ "enabled": "true", "url": "<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>", "secret": "<WEBHOOK_HMAC_SECRET>", "eventTypes": ["*"] }'
Remplacez les éléments suivants :
<KEYCLOAK_HOST>: nom d'hôte de votre serveur Keycloak<REALM_NAME>: nom du domaine à surveiller (par exemple,masteroumy-realm)<ENDPOINT_URL>: URL du point de terminaison du flux Chronicle copiée précédemment<API_KEY>: clé API Google Cloud créée précédemment<SECRET_KEY>: clé secrète du webhook Chronicle générée précédemment<WEBHOOK_HMAC_SECRET>: chaîne secrète arbitraire pour la signature HMAC des charges utiles de webhook (par exemple,mySecretKey123)
Étape 3 : Validez le webhook
Vérifiez que le webhook a été créé en listant tous les webhooks pour le domaine :
curl -sS -X GET "https://<KEYCLOAK_HOST>/realms/<REALM_NAME>/webhooks" \ -H "Authorization: Bearer ${TOKEN}" \ -H "Accept: application/json"
La réponse renvoie une liste d'objets de webhook. Vérifiez que votre webhook s'affiche avec "enabled": "true" et l'URL correcte.
Types d'événements de webhook
Le champ eventTypes accepte un tableau d'expressions permettant de filtrer les événements à envoyer :
*: envoie tous les événements (recommandé pour l'intégration SIEM)access.*: envoie tous les événements d'accèsadmin.*: envoie tous les événements d'administration.admin.USER-*: envoie tous les événements d'administration liés aux utilisateurs.admin-USER-CREATE: n'envoyer que les événements d'administration de création d'utilisateur
Format de la charge utile du webhook
Le webhook envoie des événements sous forme de requêtes HTTP POST avec des charges utiles JSON. Exemple de charge utile d'événement utilisateur :
{ "id": "987865-1a2b-3c4d-9876-654321abc", "time": 1767799710612, "type": "LOGIN", "realmId": "12345abcde-1a2b-4d3c-9876-abcd456", "clientId": "account-console", "userId": "abcd456-1234-5678-abc9-987gfed654", "sessionId": "efghij-9876-abcd-456-11223344", "ipAddress": "203.0.113.45", "details": { "auth_method": "openid-connect", "auth_type": "code", "redirect_uri": "https://app.example.com/callback", "consent": "no_consent_required", "username": "jdoe" } }
Comportement de nouvelle tentative du webhook
L'extension utilise un intervalle exponentiel entre les tentatives automatiques lorsqu'une réponse autre que 2xx est reçue :
| Paramètre | Valeur par défaut | Description |
|---|---|---|
| backoffInitialInterval | 500 ms | Intervalle initial avant une nouvelle tentative |
| backoffMaxElapsedTime | 900 000 ms (15 min) | Durée totale maximale des nouvelles tentatives |
| backoffMaxInterval | 180 000 ms (3 min) | Intervalle maximal entre les nouvelles tentatives |
| backoffMultiplier | 5 | Multiplicateur pour chaque intervalle de nouvelle tentative |
| backoffRandomizationFactor | 0,5 | Facteur de randomisation pour le jitter |
Référence des méthodes d'authentification
Les flux de webhook Chronicle sont compatibles avec plusieurs méthodes d'authentification. Choisissez la méthode acceptée par votre fournisseur.
Méthode 1 : En-têtes personnalisés (recommandée)
Si votre fournisseur accepte les en-têtes HTTP personnalisés, utilisez cette méthode pour une meilleure sécurité.
Format de la demande :
POST <ENDPOINT_URL> HTTP/1.1 Content-Type: application/json x-goog-chronicle-auth: <API_KEY> x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Avantages :
- Clé API et code secret non visibles dans l'URL
- Plus sécurisé (les en-têtes ne sont pas enregistrés dans les journaux d'accès au serveur Web)
- Méthode privilégiée lorsque le fournisseur la prend en charge
Méthode 2 : Paramètres de requête
Si votre fournisseur n'accepte pas les en-têtes personnalisés, ajoutez les identifiants à l'URL.
Format de l'URL :
<ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY>Exemple :
https://malachiteingestion-pa.googleapis.com/v2/unstructuredlogentries:batchCreate?key=AIzaSyD...&secret=abcd1234...Format de la demande :
POST <ENDPOINT_URL>?key=<API_KEY>&secret=<SECRET_KEY> HTTP/1.1 Content-Type: application/json { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Inconvénients :
- Identifiants visibles dans l'URL
- Peut être consigné dans les journaux d'accès au serveur Web
- Moins sécurisé que les en-têtes
Méthode 3 : Hybride (URL + en-tête)
Certaines configurations utilisent une clé API dans l'URL et une clé secrète dans l'en-tête.
Format de la demande :
POST <ENDPOINT_URL>?key=<API_KEY> HTTP/1.1 Content-Type: application/json x-chronicle-auth: <SECRET_KEY> { "event": "data", "timestamp": "2025-01-15T10:30:00Z" }
Noms des en-têtes d'authentification
Chronicle accepte les noms d'en-tête suivants pour l'authentification :
Pour la clé API :
x-goog-chronicle-auth(recommandé)X-Goog-Chronicle-Auth(non sensible à la casse)
Pour la clé secrète :
x-chronicle-auth(recommandé)X-Chronicle-Auth(non sensible à la casse)
Limites et bonnes pratiques concernant les webhooks
Limites de requêtes
| Limite | Valeur |
|---|---|
| Taille maximale de la requête | 4 Mo |
| RPS (requêtes par seconde) max. | 15 000 |
| Délai avant expiration de la requête | 30 seconds |
| Comportement de nouvelle tentative | Automatique avec intervalle exponentiel entre les tentatives |
Table de mappage UDM
| Champ de journal | Mappage UDM | Logique |
|---|---|---|
| payload.client_id | additional.fields | Fusionné avec les champs créés à partir de payload.client_id et payload.realm_id |
| payload.realm_id | additional.fields | |
| source_timestamp | metadata.event_timestamp | Analysé à l'aide du filtre de date avec les modèles ISO8601 et yyyy-MM-dd'T'HH:mm:ss.SSSZ |
| payload.ip_address | metadata.event_type | Défini sur "STATUS_UPDATE" si payload.ip_address n'est pas vide, sur "USER_UNCATEGORIZED" si uuid n'est pas vide, ou sur "GENERIC_EVENT" |
| uuid | metadata.event_type | |
| payload.type | metadata.product_event_type | Valeur copiée directement |
| payload.session_id | network.session_id | Valeur copiée directement |
| payload.ip_address | principal.ip | Valeur copiée directement |
| source_metadata.schema | principal.resource.attribute.labels | Fusionné avec les libellés créés à partir de source_metadata.schema, source_metadata.table, source_metadata.is_deleted (converti en chaîne), source_metadata.change_type, source_metadata.tx_id, source_metadata.lsn |
| source_metadata.table | principal.resource.attribute.labels | |
| source_metadata.is_deleted | principal.resource.attribute.labels | |
| source_metadata.change_type | principal.resource.attribute.labels | |
| source_metadata.tx_id | principal.resource.attribute.labels | |
| source_metadata.lsn | principal.resource.attribute.labels | |
| uuid | principal.user.userid | Valeur copiée directement |
| objet | security_result.detection_fields | Fusionné avec les libellés créés à partir de l'objet, read_method et payload.id |
| read_method | security_result.detection_fields | |
| payload.id | security_result.detection_fields | |
| redirect_uri | target.url | Valeur copiée directement |
| nom d'utilisateur | target.user.userid | Valeur copiée directement |
| metadata.product_name | metadata.product_name | Définissez-le sur "KEYCLOAK". |
| metadata.vendor_name | metadata.vendor_name | Définissez-le sur "KEYCLOAK". |
username" from "details_json |
target.user.userid |
Mappé à partir du journal des modifications |
redirect_uri" from "details_json |
target.url |
Mappé à partir du journal des modifications |
realm_id" and "client_id |
additional.fields |
Mappé à partir du journal des modifications |
Journal des modifications
Afficher le journal des modifications pour ce parseur
Vous avez encore besoin d'aide ? Obtenez des réponses de membres de la communauté et de professionnels Google SecOps.