Aller au contenu

Auditeur — récupérer et vérifier les preuves

Cette page suppose que l’auditeur a son propre compte dans /auditor avec scope lecture seule. Le client ne donne aucun accès shell et aucun admin tableau de bord — toute preuve est signée et vérifiable hors-ligne.

1. Audit trimestriel NIS2 — qu’est-ce que j’ai

Dans le tableau de bord

  1. Login en auditeur dans /auditor → Sidebar → Audit Packs
  2. Télécharger .jsonl.gz + .pdf + .sig pour le mois
  3. Cliquer ‘Download verify-CLI’ pour obtenir le vérifieur offline
  4. Lancer CLI contre pubkey — quatre checkmarks vertes

Ouvrez /audit-packs. Une ligne par mois pour le tenant choisi. Téléchargez :

  • 2026-04.jsonl.gz — tous les events bruts (lisible machine)
  • 2026-04.pdf — rapport lisible humain, 12-15 pages
  • 2026-04.sig — signature Ed25519 sur manifest_hash

Vérifier localement (aucun aller-retour réseau vers monsys) :

Ou via API (avancé — pour automatisation)

Fenêtre de terminal
wget https://get.monsys.ai/monsys-verify-eat-linux-x64
chmod +x monsys-verify-eat-linux-x64
./monsys-verify-eat-linux-x64 verify-pack \
--pack 2026-04.jsonl.gz \
--sig 2026-04.sig \
--pubkey https://transparency.monsys.ai/pubkeys/hub.pub

Ce que la CLI vérifie :

  1. Calcule manifest_hash = sha256(manifest) — doit matcher le contenu de .sig
  2. Parcourt chaque ligne dans JSONL.gz, chaîne sha256 → doit terminer sur hash_chain_root du manifest
  3. Vérifie la signature Ed25519 contre la pubkey publiée du hub

Sortie en cas de succès :

✓ manifest_hash matches signature
✓ hash chain root matches manifest
✓ Ed25519 signature valid against pubkey 7c34a9e2b1f0…
✓ 1247 entries in pack, 0 tampered

2. Prouver que CVE-2026-XXXX a été patchée en 7 jours

Dans le tableau de bord

  1. Sidebar → CVE noyau → rechercher CVE-2026-XXXX
  2. Onglet ‘Timeline’ montre par host : detected, patched, operator
  3. Filtre ‘Time to patch > 7 jours’ isole les SLA breaches
  4. Bouton ‘Export for audit’ → CSV avec toutes les colonnes

Trois questions d’auditeur :

  1. Quand la CVE a-t-elle été détectée la première fois ?
  2. Quand a-t-elle été patchée par hôte ?
  3. Qui a exécuté l’action ?

En un seul rapport SQL :

Ou via API (avancé — pour automatisation)

WITH first_detected AS (
SELECT agent_id, MIN(detected_at) AS first_seen
FROM kernel_cve_findings
WHERE tenant_id = $1::UUID AND cve_id = 'CVE-2026-XXXX'
GROUP BY agent_id
),
patched AS (
SELECT r.agent_id, MIN(r.boot_time) AS rebooted_at, r.eat_id
FROM kernel_reboot_history r
JOIN first_detected fd ON fd.agent_id = r.agent_id
WHERE r.tenant_id = $1::UUID
AND r.expected = true
AND r.boot_time > fd.first_seen
GROUP BY r.agent_id, r.eat_id
)
SELECT a.hostname,
fd.first_seen,
p.rebooted_at,
p.rebooted_at - fd.first_seen AS time_to_patch,
u.email AS operator
FROM first_detected fd
LEFT JOIN patched p ON p.agent_id = fd.agent_id
LEFT JOIN agents a ON a.id = fd.agent_id
LEFT JOIN emergency_tokens et ON et.id = p.eat_id
LEFT JOIN users u ON u.id = et.user_id
ORDER BY time_to_patch NULLS FIRST;

Une ligne avec time_to_patch IS NULL = hôte non encore patché. Une ligne avec operator IS NULL = redémarrage non déclenché par EAT (manuel) — preuve plus faible, mais toujours une ligne dans kernel_reboot_history avec expected=false.


3. Montrez-moi chaque action privilégiée du dernier trimestre

Dans le tableau de bord

  1. Sidebar → Audit log (sous MANAGE)
  2. Filtre : event_type = emergency_token_issued + plage Q1
  3. Par ligne : operator, target, actions JSON, exit_code
  4. Bouton ‘Verify in transparency log’ ouvre vérifieur externe par nonce

Ou via API (avancé — pour automatisation)

SELECT et.issued_at,
u.email AS operator,
a.hostname AS target,
et.level AS escalation,
et.actions::TEXT AS actions,
et.reason AS reason,
(et.result->>'exit_code')::INT AS exit_code
FROM emergency_tokens et
LEFT JOIN users u ON u.id = et.user_id
LEFT JOIN agents a ON a.id = et.agent_id
WHERE et.tenant_id = $1::UUID
AND et.issued_at >= '2026-01-01' AND et.issued_at < '2026-04-01'
ORDER BY et.issued_at DESC;

Pour vérification hors-ligne : chaque nonce est dans le journal de transparence. L’auditeur peut valider indépendamment :

Fenêtre de terminal
./monsys-verify-eat-linux-x64 verify-eat \
--nonce 7a3c… \
--log-endpoint https://transparency.monsys.ai/api/v1/transparency-log/entry \
--pubkey https://transparency.monsys.ai/pubkeys/hub.pub

→ Confirme que l’EAT a été émis par la clé légitime du hub ET que l’entrée du journal n’a pas été manipulée rétroactivement (vérification de la chaîne de hash).


4. Quels contrôles NIS2/CRA sont insuffisants

Dans le tableau de bord

  1. Sidebar → Compliance → choisir framework NIS2
  2. Matrice par contrôle : coverage + evidence count + reviewed status
  3. Filtre reviewed_status=‘draft’ pour voir le travail de review restant
  4. Bouton ‘Export as CSV’ pour rapport trimestriel

/compliance/NIS2 affiche une matrice :

ContrôleCouverturePreuvesReviewé
NIS2-Art21-2-aautomatic247
NIS2-Art21-2-bpartial12draft
NIS2-Art21-2-cmanual0draft

coverage_level=automatic = monsys exécute la requête de preuves chaque nuit et stocke les résultats dans compliance_evidence. partial = mix d’automatique + attestation manuelle. manual = champ texte pour déclaration humaine uniquement.

reviewed_status='draft' signifie : pas encore validé légalement. Tant que ce flag n’est pas reviewed ET COMPLIANCE_PRODUCTION=1 sur le hub, le PDF affiche une bannière d’avertissement “draft mapping — not suitable for legal claims”.

Pour le rapport trimestriel, exportez la matrice en CSV via :

Ou via API (avancé — pour automatisation)

Fenêtre de terminal
curl 'https://app.monsys.ai/api/v1/compliance/coverage?framework=NIS2&format=csv' \
-H "Authorization: Bearer $TOKEN" > nis2-coverage-2026-Q1.csv

5. Vérifier l’intégrité cryptographique d’une ligne de preuve spécifique

Dans le tableau de bord

  1. Sidebar → Audit Packs → choisir le mois
  2. Cliquer ‘Tamper check’ dans la vue download
  3. Coller la ligne JSONL suspecte + numéro de ligne
  4. UI montre expected vs computed hash + verdict

Soit l’auditeur doute que la ligne #847 de 2026-04.jsonl.gz ait réellement été enregistrée telle quelle, ou si l’opérateur l’a modifiée après coup.

Ou via API (avancé — pour automatisation)

Fenêtre de terminal
zcat 2026-04.jsonl.gz | sed -n '847p' > suspicious-line.json
sha256sum suspicious-line.json
# 7c34a9e2b1f0…

Comparez avec le hash_chain du manifest — chaque ligne JSONL ajoute son sha256 à la chaîne :

Fenêtre de terminal
zcat 2026-04.jsonl.gz | ./monsys-verify-eat-linux-x64 chain-position --line 847
# expected_position: 7c34a9e2b1f0…
# computed_position: 7c34a9e2b1f0…
# ✓ line 847 matches the chain

Mismatch = preuve de manipulation rétroactive. Match = la ligne est identique à ce qu’elle était à l’écriture à signed_at.


6. Quels utilisateurs ont des droits admin transversaux

Dans le tableau de bord

  1. Sidebar → RBAC → onglet ‘Admin overview’
  2. Par user : tous hosts + EATs exécutés 90 derniers jours
  3. Trier eats_last_90d ascending — admins jamais utilisés
  4. Cliquer ‘Downgrade to editor’ pour least-privilege

Ou via API (avancé — pour automatisation)

SELECT u.email,
ARRAY_AGG(DISTINCT a.hostname) FILTER (WHERE a.hostname IS NOT NULL) AS hosts,
ARRAY_AGG(DISTINCT iu.username) AS local_users,
COUNT(DISTINCT et.id) AS eats_last_90d
FROM users u
LEFT JOIN role_assignments r ON r.user_id = u.id
LEFT JOIN inventory_users iu ON iu.email = u.email -- explicit link, no auto-correlate
LEFT JOIN agents a ON a.id = iu.agent_id
LEFT JOIN emergency_tokens et ON et.user_id = u.id
AND et.issued_at >= NOW() - INTERVAL '90 days'
WHERE u.tenant_id = $1::UUID
AND r.role = 'admin'
GROUP BY u.email
ORDER BY eats_last_90d DESC NULLS LAST;

Ce que vous repérez :

  • Admins qui n’ont jamais exécuté un EAT → candidat à downgrade
  • Admins sur plus d’hôtes que strictement nécessaire → check moindre privilège
  • Admins sans lien utilisateur local → tableau de bord uniquement, pas de shell

7. Preuves de backup par hôte sur les 90 derniers jours

Dans le tableau de bord

  1. Sidebar → Inventaire → onglet Backups → filtre tag ‘production’
  2. Par host : successful_runs_90d + failed_runs_90d
  3. Trier ascending sur successful — 0 en prod = audit finding
  4. Cliquer ‘Export evidence’ pour rapport ISO 27001 A.8.13

Ou via API (avancé — pour automatisation)

SELECT a.hostname,
b.tool,
b.destination,
b.last_successful_run,
(SELECT COUNT(*) FROM backup_inventory bi
WHERE bi.agent_id = b.agent_id
AND bi.run_at >= NOW() - INTERVAL '90 days'
AND bi.success = true) AS successful_runs_90d,
(SELECT COUNT(*) FROM backup_inventory bi
WHERE bi.agent_id = b.agent_id
AND bi.run_at >= NOW() - INTERVAL '90 days'
AND bi.success = false) AS failed_runs_90d
FROM backup_configs b
JOIN agents a ON a.id = b.agent_id
WHERE b.tenant_id = $1::UUID
AND 'production' = ANY(a.tags)
ORDER BY successful_runs_90d ASC;

successful_runs_90d = 0 sur un hôte de prod est un audit finding (ISO 27001 A.8.13 / NIS2 §2(c) continuité d’activité).


8. Check de rétention — combien de temps gardons-nous les preuves

Dans le tableau de bord

  1. Sidebar → Trust Score → composant ‘Evidence continuity’
  2. Table montre par table de preuves : ligne la plus ancienne + récente
  3. À l’œil : vert >12m, rouge <12m (NIS2 minimum)
  4. Cliquer ligne rouge → détails + suggestions remediation

Ou via API (avancé — pour automatisation)

SELECT 'audit_log' AS table_name,
MIN(created_at) AS oldest, MAX(created_at) AS newest,
COUNT(*) AS rows
FROM audit_log WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'emergency_tokens',
MIN(issued_at), MAX(issued_at), COUNT(*)
FROM emergency_tokens WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'transparency_log',
MIN(appended_at), MAX(appended_at), COUNT(*)
FROM transparency_log WHERE tenant_id=$1::UUID
UNION ALL
SELECT 'audit_packs',
MIN(month_start), MAX(month_start), COUNT(*)
FROM audit_packs WHERE tenant_id=$1::UUID;

NIS2 exige (selon la transposition belge) : minimum 12 mois pour les preuves d’audit. Le composant evidence_continuity du Trust Score pénalise les tenants avec moins de 12 mois de continuum — visible dans /trust-score/v12/tenant.