Journal d'audit et métriques¶
Le contrôleur tient un journal d'audit des changements qui touchent à la sécurité et sert
des métriques Prometheus sur /metrics. Les deux lisent la base partagée avec le serveur
MCP : toutes les répliques répondent la même chose.
Journal d'audit¶
Chaque changement ci-dessous écrit un événement dans la même transaction que le changement lui-même : un événement existe exactement quand le changement a été validé.
| Action | Écrite quand |
|---|---|
finding.status_changed |
Une personne enregistre une décision de triage dans l'interface web (PATCH /api/v1/findings/{id}) : statut précédent, nouveau statut, motif, date d'expiration et proposition d'agent tranchée |
finding.status_proposed |
Un agent propose un statut avec propose_finding_status (jamais appliqué) |
finding.proposal_rejected |
Un trieur rejette la proposition d'un agent |
finding.commented |
Une personne commente un finding (POST /api/v1/findings/{id}/comments) : le commentaire, secrets masqués |
finding.severity_changed |
Un analyste de sécurité relève la criticité d'un finding, ou rétablit celle du rapport (PATCH /api/v1/findings/{id}/severity) : criticité précédente, nouvelle et d'origine, justification |
token.created |
Un jeton est créé (CLI token create, écran Jetons) |
token.rotated |
Le secret d'un jeton est remplacé (token rotate, bouton Renouveler), avec le délai de grâce |
token.revoked |
Un jeton est révoqué |
token.projects_changed |
Un administrateur change les projets d'un jeton (bouton Projets de l'écran Jetons), avec les projets précédents (null : tous les projets) |
enrollment.requested |
Un client demande un projet (enrôlement), avec le demandeur, son adresse et si une nouvelle demande a été déposée (requested) |
enrollment.approved |
Un utilisateur portant projects.create valide un projet en attente (son groupe dans le détail) |
enrollment.rejected |
Un utilisateur refuse un projet en attente, ou il expire (acteur enrollment, reason : expired) |
role.created, role.updated, role.deleted |
Un administrateur crée, modifie (avec les permissions précédentes) ou supprime un rôle |
group.created, group.updated, group.deleted |
Un administrateur crée, renomme ou modifie, ou supprime un groupe ; la validation d'un enrôlement crée un groupe de projet manquant (project dans le détail) |
group.roles_changed, group.members_changed |
Un administrateur remplace les rôles (avec leurs projets) ou les membres (identifiants ajoutés et retirés) d'un groupe |
project.created |
Un utilisateur avec projects.create crée un projet (son groupe de projet et l'identifiant de l'utilisateur dans le détail) |
project.deleted |
Un utilisateur avec projects.delete supprime un projet (nombre de rapports, de findings et de jetons, groupes supprimés ou modifiés dans le détail) |
user.groups_changed |
Un administrateur remplace les groupes donnés à un compte, par exemple pour l'ajouter à un groupe de projet (identifiants de groupes ajoutés et retirés) |
user.roles_changed |
Anciens rôles directs d'un compte (roles : rôle ou rôle:projet,projet) : plus enregistré depuis que les comptes n'ont plus de rôle propre, gardé dans l'historique |
user.login |
Un utilisateur web se connecte (méthode password ou oidc, adresse) |
user.login_failed |
Une connexion échoue : mot de passe incorrect, connexion OIDC refusée (méthode, adresse, et l'identifiant seulement s'il est valide) |
user.logout |
Un utilisateur web se déconnecte avec Déconnexion (adresse) |
audit_syslog.changed |
Le serveur syslog est modifié sur la page Journal d'audit (adresse et facility définies, null : valeur enregistrée supprimée) |
Chaque événement porte sa date, son acteur, sa cible (finding et son id, token et son
id, project et son nom), le projet quand il y en a un, et des détails en JSON.
L'acteur est :
user:<nom>: une personne connectée à l'interface web (compte local ou OIDC) ;token:<label>: un jeton Bearer (agent, client de l'API, jeton administrateur) ;cli: une commandesecurity-review-controller;anonymous: l'endpoint public d'enrôlement, ou une connexion échouée ;stdio: un agent connecté au serveur MCP en stdio.
Les détails ne contiennent jamais de secret : aucun jeton, aucun mot de passe, et le texte libre (motifs, demandeur) est masqué comme dans les rapports. Personne ne modifie un événement : aucun outil MCP ni aucune route ne les change.
Consulter le journal¶
Les administrateurs le consultent dans l'écran Journal d'audit de l'interface web (filtres
par événement et par projet), avec GET /api/v1/audit (permission audit.read ; paramètres action,
project, actor, since, limit, offset, du plus récent au plus ancien) ou en ligne de
commande :
security-review-controller audit list --action token.rotated --since 2026-10-01
security-review-controller audit list --project shop-api --actor user:alice --limit 20
# {"total": 1, "events": [{"id": 12, "created_at": "…", "action": "finding.status_changed",
# "actor": "user:alice", "target_type": "finding", "target_id": "42", "project": "shop-api",
# "details": {"previous_status": "open", "status": "accepted", "reason": "…", …}}]}
--limit (et le paramètre limit) est plafonné par SECREVIEW_MAX_LIST_LIMIT.
Conservation¶
serve supprime les événements de plus de SECREVIEW_AUDIT_RETENTION_DAYS jours (365 par
défaut), au démarrage puis chaque jour ; 0 conserve tous les événements. Avec
SECREVIEW_AUDIT_MAX_EVENTS (0 par défaut, sans limite), la même purge ne garde ensuite qu'au
plus ce nombre d'événements, en supprimant les plus anciens. La même purge se lance à la demande :
security-review-controller audit prune # SECREVIEW_AUDIT_RETENTION_DAYS et SECREVIEW_AUDIT_MAX_EVENTS
security-review-controller audit prune --days 730 # une autre durée, pour cette exécution seulement
# {"retention_days": 730, "max_events": 0, "deleted": 0}
security-review-controller audit prune --max-events 100000
Envoi vers syslog¶
Le contrôleur peut aussi envoyer chaque événement à un serveur syslog, pour le conserver hors de la base (SIEM, journal central). Désactivé par défaut :
SECREVIEW_AUDIT_SYSLOG_ADDRESS=udp://syslog.example.com:514 # ou tcp://hôte:port, unix:///dev/log
SECREVIEW_AUDIT_SYSLOG_FACILITY=local0 # par défaut
Les administrateurs (permission settings.manage) peuvent aussi le régler sur la page Journal
d'audit de l'interface web, avec le bouton Syslog à côté de Actualiser (icône d'envoi
cochée quand les événements sont envoyés, barrée sinon ; son infobulle indique le serveur). Il
ouvre la fenêtre Envoi vers syslog : Aucun envoi ou Défini ici (udp://hôte:port ou
tcp://hôte:port), et la facility. Une variable définie est
prioritaire : son champ est alors verrouillé, et une valeur définie là auparavant est ignorée
(Supprimer l'efface) ; un SECREVIEW_AUDIT_SYSLOG_ADDRESS vide compte comme non défini. Une
valeur définie là s'applique en une dizaine de secondes, sans redémarrage ; la modification est
elle-même inscrite au journal d'audit (audit_syslog.changed). À l'arrêt de l'envoi, les
événements en attente, cette modification comprise, partent d'abord vers l'ancien serveur : le
SIEM voit qui l'a arrêté. Un socket Unix est un fichier du déploiement : il se règle uniquement
par la variable. Par l'API : GET et PUT /api/v1/settings/audit-syslog avec address et
facility (null supprime la valeur enregistrée, un champ absent est inchangé, un champ dont la
variable est définie est refusé avec 422).
Chaque événement est un message de sévérité info, étiqueté secreview-audit, dont le corps est
l'événement en JSON, avec les mêmes champs que audit list :
<134>secreview-audit: {"action":"user.login","actor":"user:alice","created_at":"…","details":{"address":"10.0.0.8","method":"password"},"id":12,"project":null,"target_id":"2","target_type":"user"}
- Le contrôleur envoie les événements de tous les processus, serveur MCP et CLI compris, depuis la base : le serveur MCP ne fait toujours aucune connexion sortante. Un événement part environ 5 à 15 secondes après son écriture ; l'historique antérieur à l'activation de l'envoi (au démarrage ou dans l'interface web) n'est pas envoyé.
- Chaque événement est envoyé une fois, même avec plusieurs répliques du contrôleur : l'id du dernier événement envoyé est conservé en base, et une réplique l'avance avant d'envoyer. Un lot dont l'envoi échoue est journalisé et n'est pas renvoyé (au plus une fois).
- Ni TLS ni proxy : pour chiffrer, envoyez à un relais local (
rsyslog,syslog-ng, Fluent Bit) qui transmet en TLS. Une variable invalide arrêteserveau démarrage ; une adresse invalide est refusée par l'interface web.
Métriques Prometheus¶
GET /metrics sur le contrôleur répond au format texte de Prometheus (0.0.4). Le serveur
MCP n'a pas d'endpoint de métriques. Il faut un jeton de rôle metrics, qui n'atteint rien
d'autre (ni l'API REST, ni MCP), ou admin :
security-review-controller token create --label prometheus --role metrics --expires-in-days 365
curl -H "Authorization: Bearer $TOKEN" https://secreview.example.com/metrics
# prometheus.yml
scrape_configs:
- job_name: security-review
scheme: https
authorization:
credentials_file: /etc/prometheus/secreview-token
static_configs:
- targets: ["secreview.example.com"]
| Métrique | Labels | Valeur |
|---|---|---|
secreview_build_info |
version |
1 |
secreview_projects |
Projets ayant des rapports | |
secreview_reports |
Rapports stockés | |
secreview_findings |
status |
Findings par statut effectif (une décision expirée compte comme open) |
secreview_open_findings |
severity |
Findings ouverts par sévérité |
secreview_project_open_findings |
project, severity |
Findings ouverts de chaque projet |
secreview_finding_proposals_pending |
Propositions d'agents en attente d'une personne | |
secreview_tokens |
role, state |
Jetons par rôle et état (active, expired) |
secreview_tokens_expiring_soon |
Jetons actifs qui expirent dans les 14 jours | |
secreview_project_enrollments_pending |
Projets demandés par un enrôlement, en attente de validation | |
secreview_web_users |
auth_source |
Comptes web (local, oidc) |
secreview_advisories |
Avis de la base | |
secreview_advisory_last_update_timestamp_seconds |
Fin du dernier téléchargement OSV réussi (absente avant le premier) | |
secreview_auth_rejections_total |
reason |
Requêtes rejetées par l'authentification du processus qui répond, depuis son démarrage : invalid_token, token_expired, insufficient_scope |
Toutes les valeurs sauf secreview_auth_rejections_total viennent de la base. Les métriques
contiennent des nombres et des noms de projets, jamais un finding, un label de jeton ou un
secret ; les valeurs des labels sont échappées, si bien qu'un nom de projet ne peut pas ajouter
de lignes à la réponse. SECREVIEW_METRICS_ENABLED=false désactive l'endpoint (404).
Exemples d'alertes : secreview_tokens_expiring_soon > 0,
secreview_open_findings{severity="critical"} > 0,
time() - secreview_advisory_last_update_timestamp_seconds > 2 * 86400,
rate(secreview_auth_rejections_total{reason="invalid_token"}[5m]) > 1.