Webhooks et intégration SIEM

Diffusez les événements d'incidents et de politiques Zeuslock vers votre SIEM, votre chat ou votre outil d'astreinte avec des webhooks signés et des connecteurs prêts à l'emploi.

Ce que Zeuslock envoie, et quand

Zeuslock émet deux familles d'événements vers les systèmes externes : les événements d'incident (une action utilisateur qui a déclenché une politique) et les événements de politique (une modification effectuée par un opérateur dans la console). Les webhooks sont des requêtes HTTPS POST avec un corps JSON et un en-tête de signature HMAC-SHA256. Chaque endpoint configuré ne reçoit que les types d'événements auxquels vous vous abonnez, avec un filtrage optionnel par sévérité, type de détection ou groupe d'utilisateurs.

Les types d'événements actuellement émis sont les suivants :

  • incident.created — une nouvelle détection vient d'être levée par l'extension, l'agent desktop ou la CLI.
  • incident.status_changed — un opérateur a changé l'état de l'incident (ouvert, en cours d'investigation, etc.).
  • incident.resolved — l'incident a été clôturé.
  • incident.false_positive — un opérateur a marqué la détection comme faux positif ; utile pour réentraîner vos propres tableaux de bord.
  • policy.changed — une politique de détection ou de réponse a été modifiée et publiée.
  • policy.dry_run_completed — un backtest à blanc s'est terminé ; le payload contient le diff entre politique actuelle et politique proposée.

Configurer un endpoint

Ouvrez Console Opérateur → Paramètres → Webhooks → Ajouter un endpoint. Les champs demandés sont :

  1. URL — obligatoirement HTTPS. Les certificats auto-signés sont rejetés.
  2. Secret — Zeuslock génère une chaîne hexadécimale aléatoire de 32 octets. Copiez-la immédiatement ; elle n'est affichée qu'une seule fois et n'est stockée que sous forme de hachage.
  3. Types d'événements — cochez les événements souhaités. Pour un usage SOC, nous recommandons au minimum incident.created et incident.status_changed.
  4. Filtres (optionnels) — restreignez par severity (low, medium, high, critical), finding_type (par exemple aws_access_key, credit_card) ou group (n'importe quel groupe Okta/Azure AD synchronisé via SCIM).

Créez un endpoint par système aval. Mélanger Slack et Splunk sur la même URL rend l'analyse des relivraisons et la rotation du secret très pénibles par la suite.

Structure du payload

Tous les événements partagent la même enveloppe : event, event_id, delivered_at et un objet typé dont la clé correspond à la famille d'événement (incident ou policy). Exemple pour incident.created :

{
  "event": "incident.created",
  "event_id": "evt_01HX9F4A1Y7QZ2C8K3J5W6N0PD",
  "delivered_at": "2026-05-17T14:23:11Z",
  "incident": {
    "id": "inc_01HX9F4A1Y7QZ2C8K3J5W6N0PD",
    "severity": "high",
    "finding_type": "aws_access_key",
    "user": {
      "email": "alice@acme.com",
      "group": "engineering"
    },
    "destination": "chat.openai.com",
    "action": "block",
    "redacted_preview": "Can you debug this AWS error? AKIA*** ..."
  }
}

Référence des champs

ChampTypeExempleNotes
eventstringincident.createdNom d'événement pointé. Stable d'une version à l'autre.
event_idstring (ULID)evt_01HX9F4A1Y...Unique par tentative de livraison. Sert de clé de déduplication.
delivered_atstring (RFC 3339)2026-05-17T14:23:11ZUTC. Peut différer de la date de création de l'incident en cas de relivraison.
incident.idstring (ULID)inc_01HX9F4A1Y...Identifiant stable. Identique pour tous les événements d'un même incident.
incident.severityenumhighParmi low, medium, high, critical.
incident.finding_typestringaws_access_keyCorrespond au slug du détecteur ; voir la référence des détecteurs.
incident.user.emailstringalice@acme.comProvient du SSO, jamais saisi par l'utilisateur.
incident.user.groupstringengineeringNom de groupe SCIM, le cas échéant.
incident.destinationstringchat.openai.comNom d'hôte de l'outil d'IA visé par le prompt.
incident.actionenumblockParmi monitor, anonymize, block.
incident.redacted_previewstring... AKIA*** ...Toujours censuré côté serveur. Le secret brut ne quitte jamais l'endpoint.

Vérifier la signature HMAC

Chaque requête porte un en-tête X-Zeuslock-Signature de la forme sha256=<hex>. Le condensat est HMAC-SHA256(secret, raw_body), calculé sur les octets exacts du corps de la requête — ne resérialisez pas. Rejetez tout ce qui ne correspond pas, et rejetez tout ce qui dépasse cinq minutes en comparant delivered_at à votre horloge.

Python

import hmac, hashlib
from datetime import datetime, timezone

SECRET = b"votre-secret-hex-32-octets"

def verifier(raw_body: bytes, signature_header: str, delivered_at: str) -> bool:
    if not signature_header.startswith("sha256="):
        return False
    attendu = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
    recu = signature_header.split("=", 1)[1]
    if not hmac.compare_digest(attendu, recu):
        return False
    envoye = datetime.fromisoformat(delivered_at.replace("Z", "+00:00"))
    return abs((datetime.now(timezone.utc) - envoye).total_seconds()) < 300

Node.js

import crypto from "node:crypto";

const SECRET = "votre-secret-hex-32-octets";

export function verifier(rawBody, signatureHeader, deliveredAt) {
  if (!signatureHeader?.startsWith("sha256=")) return false;
  const attendu = crypto
    .createHmac("sha256", SECRET)
    .update(rawBody)
    .digest("hex");
  const recu = signatureHeader.slice("sha256=".length);
  const a = Buffer.from(attendu, "hex");
  const b = Buffer.from(recu, "hex");
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return false;
  const ecart = Math.abs(Date.now() - Date.parse(deliveredAt));
  return ecart < 5 * 60 * 1000;
}

Go

package webhook

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "strings"
    "time"
)

var secret = []byte("votre-secret-hex-32-octets")

func Verifier(rawBody []byte, sigHeader, deliveredAt string) bool {
    if !strings.HasPrefix(sigHeader, "sha256=") {
        return false
    }
    mac := hmac.New(sha256.New, secret)
    mac.Write(rawBody)
    attendu := mac.Sum(nil)
    recu, err := hex.DecodeString(strings.TrimPrefix(sigHeader, "sha256="))
    if err != nil || !hmac.Equal(attendu, recu) {
        return false
    }
    envoye, err := time.Parse(time.RFC3339, deliveredAt)
    if err != nil {
        return false
    }
    return time.Since(envoye).Abs() < 5*time.Minute
}

Utilisez toujours une comparaison à temps constant (hmac.compare_digest, crypto.timingSafeEqual, hmac.Equal). Un simple == laisse fuiter le secret au fil du temps via une analyse temporelle.

Relivraisons et idempotence

Si votre endpoint renvoie autre chose qu'un code 2xx, ou si le délai d'attente de 10 secondes est dépassé, Zeuslock réessaie selon ce backoff : 1 minute, 5 minutes, 30 minutes, 2 heures, 12 heures. Après cinq échecs consécutifs, l'endpoint est automatiquement suspendu, une bannière apparaît dans la Console Opérateur, et une alerte Slack est envoyée à votre canal de sécurité si vous avez connecté Slack.

Chaque relivraison réutilise le même event_id. Traitez cet identifiant comme clé primaire dans votre stockage aval et vous ne compterez jamais un incident deux fois, même pendant une panne partielle. Le corps et la signature sont identiques au bit près d'une tentative à l'autre.

Intégrations directes

Vous n'avez pas besoin d'un récepteur sur mesure pour les cibles courantes. Chacune des intégrations suivantes se configure sous Paramètres → Intégrations :

  • Slack — installation OAuth, choix du canal, filtre de sévérité optionnel. Une configuration courante consiste à n'envoyer que les sévérités high et critical dans #alertes-securite et le reste dans #zeuslock-firehose.
  • Microsoft Teams — collez une URL de webhook entrant. Utilisez un canal dédié ; le connecteur publie des cartes adaptatives qui peuvent saturer un canal général.
  • PagerDuty — clé de routage Events API v2. La sévérité est mappée directement : critical déclenche un page, high ouvre un incident sans page, les sévérités inférieures sont ignorées.
  • Splunk HEC — URL HEC et token, sourcetype zeuslock:incident. Les événements arrivent en JSON ; construisez vos tableaux de bord à partir de finding_type et user.group.
  • Microsoft Sentinel — ID du workspace Log Analytics et clé partagée. La table de log personnalisée est Zeuslock_Incidents_CL. Des requêtes de hunting et un modèle de règle analytique sont livrés avec le connecteur.
  • Adaptateur webhook générique — pour tout le reste (Datadog, Elastic, Chronicle, bus interne). Même enveloppe signée que ci-dessus.

Tester votre endpoint

Utilisez le bouton Envoyer un événement de test à côté de chaque endpoint dans la console. Il déclenche un incident.created synthétique avec un event_id préfixé evt_test_ pour que vous puissiez le filtrer hors de vos tableaux de production.

Si vous préférez automatiser depuis votre CI, appelez l'API REST avec un token d'API opérateur :

curl -X POST \
  -H "Authorization: Bearer $ZEUSLOCK_API_TOKEN" \
  https://api.zeuslock.ai/v1/webhooks/wh_01HX.../test

Échecs courants : erreurs de handshake TLS (vérifiez votre chaîne de certificats), 401 renvoyé par votre propre proxy d'authentification (ajoutez les IP de sortie Zeuslock documentées dans la console à la liste d'autorisation), et signatures qui ne correspondent pas quand un framework réécrit le corps de la requête. Si votre framework parse le JSON avant votre handler, capturez d'abord les octets bruts ou la vérification HMAC échouera.