Journal d'audit
Un journal d'audit en ajout seul, borné au tenant : plus d'une centaine d'actions typées, un flux temps réel, un export SIEM et une purge de rétention qui est la seule voie de suppression autorisée.
Tout ce qui compte dans Obexal, d'une connexion à un changement de politique en passant par une délégation d'agent IA, est écrit dans un journal d'audit unique, borné à votre organisation et immuable par construction. Cette page couvre ce qui est journalisé, comment le lire (console, API, flux temps réel), comment l'exporter, et comment fonctionne la rétention.
Ce qui est journalisé
Le journal consigne plus d'une centaine d'actions typées (110 aujourd'hui). Chaque action est un identifiant stable en notation pointée : vos règles SIEM ne dépendent jamais d'un texte d'affichage. Les grandes familles :
| Famille | Exemples |
|---|---|
user.* | user.signup, user.login.success, user.login.failed, user.session.revoked |
auth.* | MFA (auth.mfa.success), passkeys (auth.webauthn.register), passwordless, connexions SAML, LDAP et sociales, resets de mot de passe, auth.account.locked |
mfa.* | cycle de vie des facteurs : mfa.totp.activate, mfa.recovery.regenerate |
oauth.* | oauth.consent.granted, oauth.token.exchange (délégation d'agent), oauth.signing_key.rotated |
admin.* | membres, rôles et groupes, paramètres du tenant, politiques d'accès, blocages d'IP, invitations, domaines personnalisés, jetons d'API, gouvernance des agents (admin.agent.policy_updated, admin.agent.secret_rotated) |
scim.* | provisioning entrant : scim.user.provisioned, scim.user.deactivated |
| agents et sécurité | agent.auto_contained, security.incident.acked |
| plateforme | tenant.self_signup, operator.tenant.suspended, webhook.endpoint.created, crypto.secrets.reencrypted |
Anatomie d'un événement
Tel que retourné par l'API, un événement ressemble à ceci :
{
"id": "b7f3c2e8-5f9a-4c1d-9e2b-7a6d54c3f0a1",
"action": "oauth.token.exchange",
"target": "agent:agent-support-bot",
"actorUserId": "8c1e35a2-90d7-4b2e-b6a1-53d92f7c44e0",
"actorEmail": "alice@example.eu",
"ip": "203.0.113.10",
"userAgent": "support-bot/1.4",
"createdAt": "2026-07-02T09:14:03Z",
"metadata": {
"agent": "agent-support-bot",
"agentName": "Support bot",
"scope": "tickets:read",
"audience": "https://api.example.eu",
"chained": false
}
}Le champ ip est résolu de façon défensive. Par défaut, c'est l'adresse du pair TCP, non usurpable. X-Forwarded-For n'est honoré que si la connexion provient réellement d'un reverse proxy déclaré dans TRUSTED_PROXIES (auto-hébergement), et c'est alors l'adresse la plus à droite qui n'est pas un proxy de confiance qui est retenue : un client ne peut jamais choisir l'IP qui sera auditée.
Immuable par construction
Le journal est en ajout seul, et cette propriété est appliquée dans la base de données elle-même, pas seulement dans le code applicatif : un trigger sur la table d'audit rejette tout UPDATE et tout DELETE. La seule exception autorisée est le purgeur de rétention, dont le DELETE s'exécute dans une transaction qui pose le drapeau app.allow_audit_purge (SET LOCAL, donc borné à cette seule transaction). Aucun autre chemin de code ne pose ce drapeau.
Consulter le journal
La console affiche le journal dans la section Audit, mise à jour en temps réel. Via l'API, GET /v1/admin/audit exige la permission audit:view, avec un jeton d'API d'administration (Authorization: Bearer obx_...) ou une session de console. Si votre organisation utilise un domaine personnalisé, il remplace accounts.obexal.com.
| Paramètre | Signification |
|---|---|
q | recherche plein texte, limitée à 200 caractères |
outcome | ok, warn ou danger (toute autre valeur est ignorée) |
limit | taille de page, 100 par défaut, 500 au maximum |
offset | décalage de pagination |
curl -sS "https://accounts.obexal.com/v1/admin/audit?q=agent&outcome=danger&limit=50" \
-H "Authorization: Bearer $OBEXAL_API_TOKEN"
# 200 -> {"events": [...], "total": 3}Flux temps réel
GET /v1/admin/audit/stream est un flux Server-Sent Events (SSE) : chaque nouvelle écriture d'audit dans votre organisation pousse un événement audit portant l'entrée en JSON, et un commentaire ping toutes les 25 secondes maintient la connexion. La console s'en sert pour rafraîchir la vue d'audit en direct ; n'importe quel client SSE fonctionne.
curl -N https://accounts.obexal.com/v1/admin/audit/stream \
-H "Authorization: Bearer $OBEXAL_API_TOKEN"
# : connecté
# event: audit
# data: {"action":"user.login.success","createdAt":"2026-07-02T09:14:03Z", ...}Exporter vers un SIEM
GET /v1/admin/audit/export?format=csv|json (permission audit:view) retourne une pièce jointe téléchargeable avec les événements les plus récents, plafonnée à 10000. Quand le plafond est atteint, la réponse porte l'en-tête X-Obexal-Truncated: true (et "truncated": true dans le corps JSON) : prenez-le comme un signal d'ingérer plus souvent, pas comme l'historique complet. Les colonnes CSV sont createdAt, action, target, actorUserId, ip, userAgent ; le format JSON inclut aussi metadata.
Pour une ingestion continue, préférez le flux SSE ou des lectures incrémentales périodiques de GET /v1/admin/audit.
Rétention et purge
La rétention est un réglage de déploiement : AUDIT_RETENTION prend une durée, et 0 (le défaut) signifie que rien n'est jamais supprimé. Quand une fenêtre de rétention est définie, un purgeur supprime les entrées plus anciennes que le seuil, une fois au démarrage puis toutes les 24 heures, conformément au principe RGPD de limitation de la conservation. Cette purge est la seule voie autorisée à travers le trigger d'immuabilité, via le drapeau transactionnel app.allow_audit_purge décrit plus haut. Voir Configuration et RGPD.