diff --git a/README.md b/README.md index ff4b98b..204d08c 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Chaque clé `event.` conserve un événement compact, par exemple : ## Tests locaux -Les exemples SQL pour le Reporting WAPT sont dans [docs/WAPT-REPORTING.md](docs/WAPT-REPORTING.md). +Les exemples ci-dessous sont également conservés dans [docs/WAPT-REPORTING.md](docs/WAPT-REPORTING.md). Les tests utilisent uniquement des données synthétiques et des mocks Python; aucune commande Defender réelle ni WAPT n'est invoquée : @@ -122,6 +122,242 @@ Les tests utilisent uniquement des données synthétiques et des mocks Python; a python -m unittest discover -s tests -v ``` + +## Exemples SQL pour WAPT Reporting + +Ces requêtes de lecture ciblent l'onglet **Reporting de WAPT Enterprise 2.6** et la table `HostAuditData`. Elles utilisent le schéma v3 du paquet `comit-audit-defender`, dans la section `Microsoft Defender`. + +Le schéma v3 ne duplique plus chaque détection dans un snapshot complet. Il conserve : + +- `value_key = 'summary'` : état léger et historisé, `schema_version: 3`; +- `value_key = 'correlations'` : corrélations courantes et `correlation_level`; +- `value_key LIKE 'event.%'` : derniers états et transitions de chaque incident, chacun avec un `event_id` stable et `schema_version: 1`. + +Les requêtes sélectionnent le dernier état par poste pour `summary`, et le dernier état par incident pour `event.%`. La requête d'événements graves conserve les anciennes transitions récentes afin qu'une résolution ultérieure ne masque pas la détection grave initiale. + +### 1. Tableau de bord — tous les postes + +```sql +WITH latest AS ( + SELECT DISTINCT ON (host_id) + host_id, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key = 'summary' + ORDER BY host_id, value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + l.data #>> '{summary,audit_status}' AS audit_status, + (l.data #>> '{summary,current_risk_level}')::int AS risk_level, + l.data #>> '{summary,current_risk_category}' AS risk_category, + (l.data #>> '{summary,active_count}')::int AS active_threats, + (l.data #>> '{summary,detection_count_6h}')::int AS detections_6h, + (l.data #>> '{summary,investigation_count}')::int AS investigations, + l.value_date AS last_data_change +FROM latest l +JOIN hosts h ON h.uuid = l.host_id +ORDER BY risk_level DESC NULLS LAST, h.computer_fqdn; +``` + +### 2. Postes en statut `ERROR` + +```sql +WITH latest AS ( + SELECT DISTINCT ON (host_id) + host_id, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key = 'summary' + ORDER BY host_id, value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + (l.data #>> '{summary,current_risk_level}')::int AS risk_level, + (l.data #>> '{summary,active_count}')::int AS active_count, + (l.data #>> '{collection_complete}')::boolean AS collection_complete, + l.data #> '{summary,protection_issues}' AS protection_issues, + l.value_date AS last_data_change +FROM latest l +JOIN hosts h ON h.uuid = l.host_id +WHERE l.data #>> '{summary,audit_status}' = 'ERROR' +ORDER BY risk_level DESC NULLS LAST, h.computer_fqdn; +``` + +### 3. Menaces non résolues ou demandant une investigation + +Les enregistrements sont dédupliqués au dernier état par incident. Une exécution historique peut exiger une investigation sans faire apparaître la détection comme active. + +```sql +WITH latest_events AS ( + SELECT DISTINCT ON (host_id, value_key) + host_id, + value_key, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key LIKE 'event.%' + ORDER BY host_id, value_key, value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + e.data ->> 'event_id' AS event_id, + e.data ->> 'event_type' AS event_type, + (e.data ->> 'event_level')::int AS event_level, + e.data ->> 'remediation_state' AS remediation_state, + (e.data ->> 'active')::boolean AS active, + (e.data ->> 'execution_observed')::boolean AS execution_observed, + (e.data ->> 'requires_investigation')::boolean AS requires_investigation, + e.data ->> 'timestamp' AS event_time, + e.value_date AS last_reported +FROM latest_events e +JOIN hosts h ON h.uuid = e.host_id +WHERE COALESCE((e.data ->> 'active')::boolean, false) + OR COALESCE((e.data ->> 'requires_investigation')::boolean, false) +ORDER BY event_level DESC NULLS LAST, h.computer_fqdn; +``` + +La colonne `last_reported` permet d'identifier un poste dont l'état n'a pas été actualisé récemment. + +### 4. Événements de niveau élevé observés dans les six dernières heures + +La requête conserve une ligne par incident parmi les transitions dont l'heure d'événement est récente. Une transition résolue de faible score ne masque donc pas une transition grave précédente dans la fenêtre. + +```sql +WITH recent_events AS ( + SELECT DISTINCT ON (host_id, value::jsonb ->> 'event_id') + host_id, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key LIKE 'event.%' + AND (value::jsonb ->> 'event_level')::int >= 10 + AND NULLIF(value::jsonb ->> 'timestamp', '')::timestamptz + >= NOW() - INTERVAL '6 hours' + ORDER BY host_id, value::jsonb ->> 'event_id', value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + e.data ->> 'event_id' AS event_id, + e.data ->> 'event_type' AS event_type, + (e.data ->> 'event_level')::int AS event_level, + e.data ->> 'remediation_state' AS remediation_state, + (e.data ->> 'active')::boolean AS active, + (e.data ->> 'timestamp')::timestamptz AS event_time, + e.value_date AS recorded_at +FROM recent_events e +JOIN hosts h ON h.uuid = e.host_id +ORDER BY event_level DESC, event_time DESC; +``` + +L'heure de fenêtre est celle du serveur PostgreSQL. Le paquet conserve les événements sans nom de menace ni chemin utilisateur afin de limiter les données transmises. + +### 5. État des protections + +```sql +WITH latest AS ( + SELECT DISTINCT ON (host_id) + host_id, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key = 'summary' + ORDER BY host_id, value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + l.data #>> '{defender,running_mode}' AS running_mode, + (l.data #>> '{defender,antivirus_enabled}')::boolean AS antivirus_enabled, + (l.data #>> '{defender,realtime_enabled}')::boolean AS realtime_enabled, + (l.data #>> '{defender,signature_age}')::int AS signature_age_days, + l.data #> '{summary,protection_issues}' AS protection_issues, + l.value_date AS last_data_change +FROM latest l +JOIN hosts h ON h.uuid = l.host_id +WHERE jsonb_array_length(COALESCE(l.data #> '{summary,protection_issues}', '[]'::jsonb)) > 0 +ORDER BY h.computer_fqdn; +``` + +Le paquet ne signale pas à tort le mode passif comme une panne, notamment lorsqu'un autre antivirus assure la protection. + +### 6. Corrélations courantes + +```sql +WITH latest AS ( + SELECT DISTINCT ON (host_id) + host_id, + value::jsonb AS data, + value_date + FROM HostAuditData + WHERE value_section = 'Microsoft Defender' + AND value_key = 'correlations' + ORDER BY host_id, value_date DESC, ctid DESC +) +SELECT + h.uuid AS host_id, + h.computer_fqdn, + l.data ->> 'correlation_level' AS correlation_level, + c.item ->> 'rule' AS correlation_rule, + (c.item ->> 'count')::int AS related_count, + (c.item ->> 'correlation_level')::int AS rule_level, + l.value_date AS last_data_change +FROM latest l +JOIN hosts h ON h.uuid = l.host_id +CROSS JOIN LATERAL jsonb_array_elements( + COALESCE(l.data -> 'correlations', '[]'::jsonb) +) AS c(item) +ORDER BY (l.data ->> 'correlation_level')::int DESC NULLS LAST, + h.computer_fqdn; +``` + +Règles possibles : `same_threat_repeated`, `multiple_threats`, `severe_with_protection_disabled`, `repeated_remediation_failures` et `explicitly_allowed`. + +### 7. Historique du risque d'un poste + +Remplacer `PC01.example.local` par le nom DNS complet du poste. + +```sql +SELECT + h.uuid AS host_id, + h.computer_fqdn, + a.value_date AS change_date, + a.value #>> '{summary,audit_status}' AS audit_status, + (a.value #>> '{summary,current_risk_level}')::int AS risk_level, + (a.value #>> '{summary,max_event_level_6h}')::int AS max_event_level_6h, + (a.value #>> '{summary,active_count}')::int AS active_count, + (a.value #>> '{summary,correlation_level}')::int AS correlation_level +FROM HostAuditData a +JOIN hosts h ON h.uuid = a.host_id +WHERE a.value_section = 'Microsoft Defender' + AND a.value_key = 'summary' + AND h.computer_fqdn = 'PC01.example.local' +ORDER BY a.value_date DESC +LIMIT 128; +``` + +L'historique utilise `write_audit_data_if_changed()` : il contient les changements de résumé, pas chaque exécution de l'audit. Le paquet conserve au plus 128 valeurs de résumé et 32 valeurs de corrélation pendant 90 jours; chaque clé d'événement conserve au plus 16 transitions pendant 90 jours. + +### Notes et validation + +- Les requêtes sont en lecture seule et supposent les colonnes `HostAuditData.value_section`, `value_key`, `value`, `value_date`, `host_id` et les champs `hosts.uuid`, `computer_fqdn` utilisés par l'exemple fourni. +- Les identifiants d'hôte dynamiques utilisent `hosts.uuid`. +- Le schéma v2 historique n'est pas interrogé par ces exemples. Les anciennes clés v2 cessent d'être alimentées par le paquet v3 et expirent selon la rétention précédemment définie. +- Les requêtes SQL sont des exemples construits à partir du schéma du paquet et des colonnes communiquées; elles n'ont pas été exécutées sur une instance WAPT réelle. + +Références : [WAPT 2.6 — audit et Reporting](https://www.wapt.fr/fr/doc-2.6/wapt-create-packages-code.html), [WAPT Reporting avancé](https://www.wapt.fr/en/doc-2.7/wapt-console-usage-advanced-reporting.html). + ## Cloner le paquet dans le dossier WAPT de développement Dans PowerShell, créez `C:\waptdev` si nécessaire, puis clonez le dépôt en HTTPS dans `C:\waptdev\comi-audit-defender` :