Files
ja4-platform/docs/database/schema.md
toto 51dd376f7a docs: mise à jour complète — 7/8 techniques, 85 features, 12 modules
Reflète l'état réel du système après les étapes 1-9 du roadmap :

- §5.2 (fleet_detector NetworkX/Louvain) et §5.8 (Jaccard cross-domain) : 
- MetaLearner (régression logistique, fallback poids fixes) : documenté
- ExIFFI (profondeur isolation EIF) + erreur AE par feature : documenté
- KL divergence en complément du KS, drift adversarial : documenté
- HTTP/2 fingerprinting (h2_fingerprint, dict_browser_h2, axis_h2_coherence) : documenté
- Métriques de cycle (metrics.py, ml_performance_metrics, alertes) : documenté
- Browser confidence : 5 axes → 6 axes (axis_h2_coherence)
- 85 features (73 FEATURES + 12 FEATURES_COMPLET), 12 modules, 53 routes dashboard
- Conformité thèse : 99.4% (était 97.9%), §5 : 87.5% (était 62.5%)
- Tables nouvelles : fleet_detections, ml_performance_metrics, soc_feedback
- Dictionnaires : 8 (dict_browser_h2 ajouté)
- Dashboard : 16 pages + 37 API routes (fleet, health ajoutés)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-10 01:31:20 +02:00

724 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Schéma de base de données
La plateforme ja4-platform utilise ClickHouse comme entrepôt de données central.
Le schéma est réparti sur **deux bases de données** configurables via variables
d'environnement (`CLICKHOUSE_DB_LOGS`, `CLICKHOUSE_DB_PROCESSING`) :
| Variable | Défaut | Rôle |
|----------|--------|------|
| `CLICKHOUSE_DB_LOGS` | `ja4_logs` | Ingestion brute + logs HTTP parsés |
| `CLICKHOUSE_DB_PROCESSING` | `ja4_processing` | Agrégations, ML, vues, dictionnaires, audit |
Les vues matérialisées dans `ja4_processing` lisent depuis `ja4_logs` (références
inter-bases). Le schéma complet est défini dans 13 fichiers SQL ordonnés dans
`shared/clickhouse/` et déployé via `deploy_schema.sh`.
---
## Récapitulatif global
| Catégorie | Nombre | Objets |
|-----------|--------|--------|
| **Bases de données** | 2 | `ja4_logs`, `ja4_processing` |
| **Tables** | 19 | `http_logs_raw`, `http_logs`, `ref_bot_networks`, `bot_ip`, `bot_ja4`, `anubis_ip_rules`, `anubis_asn_rules`, `agg_host_ip_ja4_1h`, `agg_header_fingerprint_1h`, `agg_path_sequences_1h`, `agg_request_timing_1h`, `agg_ip_behavior_1h`, `agg_resource_cascade_1h`, `ml_detected_anomalies`, `ml_all_scores`, `audit_logs`, `fleet_detections`, `ml_performance_metrics`, `soc_feedback` |
| **Dictionnaires** | 8 | `dict_iplocate_asn`, `dict_bot_ip`, `dict_bot_ja4`, `dict_browser_ja4`, `dict_browser_h2`, `dict_asn_reputation`, `dict_anubis_ip`, `dict_anubis_asn` |
| **Vues matérialisées** | 7 | `mv_http_logs`, `mv_agg_host_ip_ja4_1h`, `mv_agg_header_fingerprint_1h`, `mv_agg_path_sequences_1h`, `mv_agg_request_timing_1h`, `mv_agg_ip_behavior_1h`, `mv_agg_resource_cascade_1h` |
| **Vues** | 9 | `view_ip_recurrence`, `view_ai_features_1h`, `view_form_bruteforce_detected`, `view_host_ip_ja4_rotation`, `view_dashboard_user_agents`, `view_dashboard_entities`, `view_resource_cascade_1h`, `view_thesis_features_1h`, `view_health_metrics` |
---
## Rétention des données (TTL)
| Table | TTL | Clé de partition |
|-------|-----|------------------|
| `http_logs_raw` | 2 heures | `toStartOfHour(ingest_time)` |
| `http_logs` | 30 jours | `toDate(log_date)` |
| `agg_host_ip_ja4_1h` | 7 jours | `toDate(window_start)` |
| `agg_header_fingerprint_1h` | 7 jours | `toDate(window_start)` |
| `agg_path_sequences_1h` | 7 jours | `toDate(window_start)` |
| `agg_request_timing_1h` | 7 jours | `toDate(window_start)` |
| `agg_ip_behavior_1h` | 7 jours | `toDate(window_start)` |
| `agg_resource_cascade_1h` | 7 jours | `toDate(window_start)` |
| `ml_detected_anomalies` | 7 jours | `toDate(detected_at)` |
| `ml_all_scores` | 7 jours | `toDate(window_start)` |
| `audit_logs` | 90 jours | `toDate(timestamp)` |
Toutes les tables d'agrégation et ML utilisent `ttl_only_drop_parts = 1` pour
une expiration efficace au niveau des partitions.
---
## Tables — Base `ja4_logs`
### http_logs_raw
Table d'ingestion brute — cible directe des INSERTs du correlator.
| Colonne | Type | Description |
|---------|------|-------------|
| `raw_json` | String CODEC(ZSTD(3)) | Log corrélé complet au format JSON |
| `ingest_time` | DateTime DEFAULT `now()` | Horodatage d'insertion |
- **Moteur** : MergeTree
- **Partition** : `toStartOfHour(ingest_time)`
- **Tri** : `ingest_time`
- **TTL** : `ingest_time + INTERVAL 2 HOUR`
- **Settings** : `index_granularity = 8192, ttl_only_drop_parts = 1`
---
### http_logs
Table de logs HTTP parsés et enrichis — alimentée par la vue matérialisée
`mv_http_logs`.
| Colonne | Type | Description |
|---------|------|-------------|
| `time` | DateTime | Horodatage de la requête |
| `log_date` | Date DEFAULT `toDate(time)` | Clé de partition |
| `src_ip` | IPv4 | IP source du client |
| `src_port` | UInt16 | Port source |
| `dst_ip` | IPv4 | IP destination du serveur |
| `dst_port` | UInt16 | Port destination |
| `src_asn` | UInt32 | ASN source (enrichi via dict_iplocate_asn) |
| `src_country_code` | LowCardinality(String) | Code pays |
| `src_as_name` | LowCardinality(String) | Nom de l'AS |
| `src_org` | LowCardinality(String) | Organisation de l'AS |
| `src_domain` | LowCardinality(String) | Domaine de l'AS |
| `method` | LowCardinality(String) | Méthode HTTP |
| `scheme` | LowCardinality(String) | Schéma URL (http/https) |
| `host` | LowCardinality(String) | En-tête Host HTTP |
| `path` | String CODEC(ZSTD(3)) | Chemin de la requête |
| `query` | String CODEC(ZSTD(3)) | Paramètres de requête |
| `http_version` | LowCardinality(String) | Version HTTP |
| `orphan_side` | LowCardinality(String) | Côté orphelin (A, B, ou vide) |
| `correlated` | UInt8 | 1 si corrélation HTTP+TLS réussie |
| `keepalives` | UInt16 | Numéro de séquence keep-alive |
| `a_timestamp` | UInt64 | Horodatage source A (ns) |
| `b_timestamp` | UInt64 | Horodatage source B (ns) |
| `conn_id` | String CODEC(ZSTD(3)) | Identifiant de connexion TCP |
| `ip_meta_df` | UInt8 | Drapeau Don't Fragment |
| `ip_meta_id` | UInt16 | Identification IP |
| `ip_meta_total_length` | UInt16 | Longueur totale IP |
| `ip_meta_ttl` | UInt8 | TTL IP |
| `tcp_meta_options` | LowCardinality(String) | Options TCP |
| `tcp_meta_window_size` | UInt32 | Taille de fenêtre TCP |
| `tcp_meta_mss` | UInt16 | MSS TCP |
| `tcp_meta_window_scale` | UInt8 | Facteur d'échelle de fenêtre TCP |
| `syn_to_clienthello_ms` | Int32 | Délai SYN→ClientHello (ms) |
| `tls_version` | LowCardinality(String) | Version TLS |
| `tls_sni` | LowCardinality(String) | SNI TLS |
| `tls_alpn` | LowCardinality(String) | ALPN TLS |
| `ja3` | String CODEC(ZSTD(3)) | Empreinte JA3 |
| `ja3_hash` | String CODEC(ZSTD(3)) | Hash MD5 JA3 |
| `ja4` | String CODEC(ZSTD(3)) | Empreinte JA4 |
| `client_headers` | String CODEC(ZSTD(3)) | Noms d'en-têtes séparés par virgule |
| `header_user_agent` | String CODEC(ZSTD(3)) | En-tête User-Agent |
| `header_accept` | String CODEC(ZSTD(3)) | En-tête Accept |
| `header_accept_encoding` | String CODEC(ZSTD(3)) | En-tête Accept-Encoding |
| `header_accept_language` | String CODEC(ZSTD(3)) | En-tête Accept-Language |
| `header_content_type` | String CODEC(ZSTD(3)) | En-tête Content-Type |
| `header_x_request_id` | String CODEC(ZSTD(3)) | En-tête X-Request-Id |
| `header_x_trace_id` | String CODEC(ZSTD(3)) | En-tête X-Trace-Id |
| `header_x_forwarded_for` | String CODEC(ZSTD(3)) | En-tête X-Forwarded-For |
| `header_sec_ch_ua` | String CODEC(ZSTD(3)) | En-tête Sec-CH-UA |
| `header_sec_ch_ua_mobile` | String CODEC(ZSTD(3)) | En-tête Sec-CH-UA-Mobile |
| `header_sec_ch_ua_platform` | String CODEC(ZSTD(3)) | En-tête Sec-CH-UA-Platform |
| `header_sec_fetch_dest` | String CODEC(ZSTD(3)) | En-tête Sec-Fetch-Dest |
| `header_sec_fetch_mode` | String CODEC(ZSTD(3)) | En-tête Sec-Fetch-Mode |
| `header_sec_fetch_site` | String CODEC(ZSTD(3)) | En-tête Sec-Fetch-Site |
| `anubis_bot_name` | LowCardinality(String) DEFAULT `''` | Nom du bot détecté par Anubis |
| `anubis_bot_action` | LowCardinality(String) DEFAULT `''` | Action Anubis |
| `anubis_bot_category` | LowCardinality(String) DEFAULT `''` | Catégorie Anubis |
| `h2_fingerprint` | String DEFAULT `''` | Fingerprint HTTP/2 au format Akamai (SETTINGS\|WU\|PRIORITY\|PSEUDO) |
| `h2_settings_fp` | String DEFAULT `''` | Valeurs brutes des paramètres SETTINGS HTTP/2 |
| `h2_window_update` | UInt32 DEFAULT 0 | Valeur WINDOW_UPDATE initial du client HTTP/2 |
| `h2_pseudo_order` | LowCardinality(String) DEFAULT `''` | Ordre des pseudo-headers (`:method:authority:scheme:path`) |
Index de saut de données :
| Index | Type | Granularité |
|-------|------|-------------|
| `idx_src_ip` | bloom_filter(0.01) | 4 |
| `idx_ja4` | bloom_filter(0.01) | 4 |
- **Moteur** : MergeTree
- **Partition** : `toDate(log_date)`
- **Tri** : `(time, src_ip, dst_ip, ja4)`
- **TTL** : `log_date + INTERVAL 30 DAY`
- **Settings** : `index_granularity = 8192, ttl_only_drop_parts = 1`
---
## Tables — Base `ja4_processing`
### Tables Anubis
Tables de règles pour la détection de crawlers Anubis.
| Table | Clé de tri | Colonnes | Moteur |
|-------|-----------|----------|--------|
| `anubis_ip_rules` | `prefix` (String) | `bot_name`, `action`, `rule_id` (UInt64), `has_ua` (UInt8), `category` | ReplacingMergeTree |
| `anubis_asn_rules` | `asn` (UInt32) | `bot_name`, `action`, `category` | ReplacingMergeTree |
> **Note** : les tables `anubis_ua_rules` et `anubis_country_rules` ainsi que les
> dictionnaires `dict_anubis_ua` et `dict_anubis_country` ont été supprimés.
> L'enrichissement Anubis repose désormais sur deux niveaux : IP/CIDR → ASN.
---
### ref_bot_networks
Table de référence des réseaux de bots connus (CIDR).
| Colonne | Type | Description |
|---------|------|-------------|
| `network` | String | Réseau CIDR |
| `bot_name` | LowCardinality(String) | Nom du bot |
| `is_legitimate` | UInt8 | 1 = bot légitime |
| `last_update` | DateTime | Dernière mise à jour |
- **Moteur** : ReplacingMergeTree(last_update)
- **Tri** : `(network, bot_name)`
---
### bot_ip / bot_ja4
Tables fichier CSV pour la recherche rapide de bots.
| Table | Colonne | Moteur |
|-------|---------|--------|
| `bot_ip` | `ip` (String) | File(CSV, `'bot_ip.csv'`) |
| `bot_ja4` | `ja4` (String) | File(CSV, `'bot_ja4.csv'`) |
---
### agg_host_ip_ja4_1h
Agrégation comportementale par `(src_ip, ja4, host)` par heure. Utilise des
colonnes `SimpleAggregateFunction` et `AggregateFunction` pour l'agrégation
incrémentale.
**Colonnes clés** : `window_start` (DateTime), `src_ip` (IPv6), `ja4` (String),
`host` (String), `src_asn` (UInt32).
**Colonnes d'agrégation (~50)** :
| Catégorie | Colonnes |
|-----------|----------|
| Compteurs (SimpleAggregateFunction sum) | `hits`, `count_post`, `orphan_count`, `ip_id_zero_count`, `mss_1460_count`, `count_assets`, `count_no_referer`, `tls12_count`, `count_head`, `count_no_sec_fetch`, `count_generic_accept`, `count_http10`, `count_no_wscale`, `count_correlated`, `count_no_accept_enc`, `count_http_scheme`, `count_xff`, `count_unusual_ct`, `count_non_std_port`, `count_login_post` |
| Valeurs uniques (AggregateFunction uniq) | `uniq_paths`, `uniq_query_params`, `unique_src_ports`, `unique_conn_id`, `uniq_ua`, `uniq_ja3` |
| Variances (AggregateFunction varPop) | `tcp_jitter_variance`, `total_ip_length_var`, `url_depth_variance`, `ip_df_var` |
| Moyennes (AggregateFunction avg/avgIf) | `avg_syn_ms`, `avg_ttl` |
| Variance conditionnelle (AggregateFunction varPopIf) | `ttl_var` |
**Projection** : `proj_by_ip` → ORDER BY `(src_ip, window_start, ja4, host)`
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip, ja4, host)`
- **TTL** : `window_start + INTERVAL 7 DAY` (partition `toDate(window_start)`)
- **Settings** : `deduplicate_merge_projection_mode = 'drop'`
---
### agg_header_fingerprint_1h
Agrégation d'empreinte d'en-têtes par `(src_ip)` par heure.
| Colonne | Type | Description |
|---------|------|-------------|
| `window_start` | DateTime | Début de la fenêtre horaire |
| `src_ip` | IPv6 | IP source |
| `header_order_hash` | SimpleAggregateFunction(any, String) | Hash de l'ordre des en-têtes |
| `header_count` | SimpleAggregateFunction(max, UInt16) | Nombre max d'en-têtes |
| `has_accept_language` | SimpleAggregateFunction(max, UInt8) | Présence Accept-Language |
| `has_cookie` | SimpleAggregateFunction(max, UInt8) | Présence Cookie |
| `has_referer` | SimpleAggregateFunction(max, UInt8) | Présence Referer |
| `modern_browser_score` | SimpleAggregateFunction(max, UInt8) | Score de conformité navigateur |
| `ua_ch_mismatch` | SimpleAggregateFunction(max, UInt8) | Incohérence UA/Client Hints |
| `sec_ch_mobile_mismatch` | SimpleAggregateFunction(max, UInt8) | Incohérence Sec-CH-UA-Mobile |
| `sec_fetch_mode` | SimpleAggregateFunction(any, String) | Valeur Sec-Fetch-Mode |
| `sec_fetch_dest` | SimpleAggregateFunction(any, String) | Valeur Sec-Fetch-Dest |
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip)`
- **TTL** : `window_start + INTERVAL 7 DAY` (partition `toDate(window_start)`)
---
### agg_path_sequences_1h (thèse §5.1)
Entropie des séquences de chemins — transitions de Markov sur les chemins normalisés.
| Colonne | Type | Description |
|---------|------|-------------|
| `window_start` | DateTime | Début de la fenêtre |
| `src_ip` | IPv6 | IP source |
| `ja4` | LowCardinality(String) | Empreinte JA4 |
| `host` | LowCardinality(String) | Hôte cible |
| `path_sequence` | AggregateFunction(groupArray(100), Tuple(UInt32, String)) | Séquence ordonnée (timestamp, chemin) |
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip, ja4, host)`
- **Partition** : `toDate(window_start)`**TTL** : 7 jours
- **Settings** : `ttl_only_drop_parts = 1`
---
### agg_request_timing_1h (thèse §5.3)
Cadence des requêtes — analyse du coefficient de variation et des bursts.
| Colonne | Type | Description |
|---------|------|-------------|
| `window_start` | DateTime | Début de la fenêtre |
| `src_ip` | IPv6 | IP source |
| `ja4` | LowCardinality(String) | Empreinte JA4 |
| `host` | LowCardinality(String) | Hôte cible |
| `request_times` | AggregateFunction(groupArrayIf(500), UInt64, UInt8) | Horodatages des requêtes (filtré a_timestamp > 0) |
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip, ja4, host)`
- **Partition** : `toDate(window_start)`**TTL** : 7 jours
- **Settings** : `ttl_only_drop_parts = 1`
---
### agg_ip_behavior_1h (thèse §5.5 / §5.8)
Dérive JA4 et comportement inter-domaines par IP.
| Colonne | Type | Description |
|---------|------|-------------|
| `window_start` | DateTime | Début de la fenêtre |
| `src_ip` | IPv6 | IP source |
| `ja4_sequence` | AggregateFunction(groupArray(200), Tuple(UInt32, String)) | Séquence temporelle (timestamp, ja4) |
| `host_hits_keys` | AggregateFunction(sumMap, Array(String), Array(UInt64)) | Distribution hôte → hits |
| `host_count` | AggregateFunction(uniq, String) | Nombre d'hôtes distincts |
| `total_hits` | SimpleAggregateFunction(sum, UInt64) | Requêtes totales |
| `first_seen` | SimpleAggregateFunction(min, DateTime) | Première observation |
| `last_seen` | SimpleAggregateFunction(max, DateTime) | Dernière observation |
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip)`
- **Partition** : `toDate(window_start)`**TTL** : 7 jours
- **Settings** : `ttl_only_drop_parts = 1`
---
### agg_resource_cascade_1h (thèse §5.4)
Arbre de dépendances de chargement de ressources.
| Colonne | Type | Description |
|---------|------|-------------|
| `window_start` | DateTime | Début de la fenêtre |
| `src_ip` | IPv6 | IP source |
| `ja4` | LowCardinality(String) | Empreinte JA4 |
| `host` | LowCardinality(String) | Hôte cible |
| `resource_loads` | AggregateFunction(groupArray(200), Tuple(UInt32, UInt8)) | Chargements (timestamp, is_asset) |
- **Moteur** : AggregatingMergeTree
- **Tri** : `(window_start, src_ip, ja4, host)`
- **Partition** : `toDate(window_start)`**TTL** : 7 jours
- **Settings** : `ttl_only_drop_parts = 1`
---
### ml_detected_anomalies
Détections d'anomalies au-dessus du seuil de menace.
**Colonnes principales** :
| Colonne | Type | Description |
|---------|------|-------------|
| `detected_at` | DateTime | Horodatage de la détection |
| `src_ip` | IPv6 | IP source |
| `ja4` | String | Empreinte JA4 |
| `host` | String | Hôte cible |
| `bot_name` | String | Nom du bot identifié |
| `browser_family` | LowCardinality(String) DEFAULT `''` | Famille de navigateur |
| `anomaly_score` | Float32 | Score d'anomalie normalisé |
| `raw_anomaly_score` | Float32 DEFAULT `0` | Score brut avant normalisation |
| `threat_level` | String | Niveau de menace (CRITICAL, HIGH, MEDIUM, LOW) |
| `model_name` | String | Nom du modèle (Complet, Applicatif, etc.) |
| `recurrence` | UInt32 | Nombre de détections précédentes |
| `campaign_id` | Int32 DEFAULT `-1` | Identifiant de campagne HDBSCAN |
| `reason` | String | Explication de la détection |
**Colonnes de contexte réseau** : `asn_number`, `asn_org`, `asn_detail`,
`asn_domain`, `country_code`, `asn_label` (tous String).
**Colonnes de features ML (~30)** : `hits`, `hit_velocity`, `fuzzing_index`,
`post_ratio`, `port_exhaustion_ratio`, `max_keepalives`, `orphan_ratio`,
`tcp_jitter_variance`, `tcp_shared_count`, `true_window_size`, `window_mss_ratio`,
`alpn_http_mismatch`, `is_alpn_missing`, `sni_host_mismatch`, `header_count`,
`has_accept_language`, `has_cookie`, `has_referer`, `modern_browser_score`,
`is_headless`, `ua_ch_mismatch`, `header_order_shared_count`, `ip_id_zero_ratio`,
`request_size_variance`, `multiplexing_efficiency`, `mss_mobile_mismatch`,
`correlated`, `asset_ratio`, `direct_access_ratio`, `is_ua_rotating`,
`distinct_ja4_count`, `src_port_density`, `ja4_asn_concentration`,
`ja4_country_concentration`, `is_rare_ja4`, `header_order_confidence`,
`distinct_header_orders`, `temporal_entropy`, `path_diversity_ratio`,
`url_depth_variance`, `anomalous_payload_ratio`.
**Colonnes Anubis** : `anubis_bot_name`, `anubis_bot_action`, `anubis_bot_category`
(LowCardinality(String) DEFAULT `''`).
**Index de saut** :
| Index | Type | Granularité |
|-------|------|-------------|
| `idx_detected_at` | minmax | 4 |
| `idx_threat_level` | set(8) | 4 |
| `idx_bot_name` | bloom_filter() | 4 |
- **Moteur** : ReplacingMergeTree(detected_at)
- **Partition** : `toYYYYMMDD(detected_at)`
- **Tri** : `(src_ip)`
- **TTL** : `detected_at + INTERVAL 7 DAY`
- **Settings** : `index_granularity = 8192, ttl_only_drop_parts = 1`
---
### ml_all_scores
Toutes les classifications ML (sans filtre de seuil) pour l'observabilité.
| Colonne | Type | Description |
|---------|------|-------------|
| `detected_at` | DateTime | Horodatage de la détection |
| `window_start` | DateTime | Début de la fenêtre d'analyse |
| `src_ip` | IPv6 | IP source |
| `ja4` | String | Empreinte JA4 |
| `host` | String | Hôte cible |
| `bot_name` | String | Nom du bot |
| `browser_family` | LowCardinality(String) DEFAULT `''` | Famille de navigateur |
| `anomaly_score` | Float32 | Score final |
| `raw_anomaly_score` | Float32 | Score brut |
| `threat_level` | String | Niveau de menace |
| `model_name` | String | Nom du modèle |
| `correlated` | UInt8 | 1 si trafic corrélé |
| `asn_number` | String | Numéro ASN |
| `asn_org` | String | Organisation ASN |
| `country_code` | String | Code pays |
| `asn_label` | String | Label de réputation ASN |
| `hits` | UInt64 | Nombre de requêtes |
| `hit_velocity` | Float32 | Vélocité des hits |
| `fuzzing_index` | Float32 | Indice de fuzzing |
| `post_ratio` | Float32 | Ratio de requêtes POST |
| `campaign_id` | Int32 | Identifiant de campagne |
| `ae_recon_error` | Float32 DEFAULT `0` | Erreur de reconstruction autoencoder |
| `xgb_prob` | Float32 DEFAULT `0` | Probabilité XGBoost supervisé |
| `anubis_bot_name` | LowCardinality(String) DEFAULT `''` | Nom du bot Anubis |
| `anubis_bot_action` | LowCardinality(String) DEFAULT `''` | Action Anubis |
| `anubis_bot_category` | LowCardinality(String) DEFAULT `''` | Catégorie Anubis |
**Index de saut** :
| Index | Type | Granularité |
|-------|------|-------------|
| `idx_detected_at` | minmax | 4 |
| `idx_threat_level` | set(8) | 4 |
- **Moteur** : ReplacingMergeTree(detected_at)
- **Partition** : `toYYYYMMDD(window_start)`
- **Tri** : `(window_start, src_ip, ja4, host, model_name)`
- **TTL** : `window_start + INTERVAL 7 DAY`
- **Settings** : `index_granularity = 8192, ttl_only_drop_parts = 1`
---
### fleet_detections
Résultats de détection de flottes de bots coordonnées via analyse de graphe bipartite JA4×ASN (module `fleet.py`).
| Colonne | Type | Description |
|---------|------|-------------|
| `src_ip` | IPv6 | Adresse IP membre de la flotte |
| `campaign_id` | Int32 | Identifiant de la communauté détectée |
| `fleet_score` | Float32 | Score de flotte : `taille × densité / log(nb_ASN)` |
| `ja4_list` | Array(String) | Empreintes JA4 partagées dans la communauté |
| `asn_list` | Array(UInt32) | ASNs impliqués dans la communauté |
| `member_count` | UInt32 | Nombre total d'IPs dans la communauté |
| `window_start` | DateTime | Début de la fenêtre de détection |
| `detected_at` | DateTime | Horodatage d'insertion |
- **Moteur** : ReplacingMergeTree(detected_at)
- **Partition** : `toDate(window_start)`
- **Tri** : `(window_start, src_ip, campaign_id)`
- **TTL** : `window_start + INTERVAL 7 DAY`
---
### ml_performance_metrics
Métriques de performance du pipeline ML par cycle de détection (module `metrics.py`).
| Colonne | Type | Description |
|---------|------|-------------|
| `model_name` | LowCardinality(String) | Nom du modèle (`complet`, `applicatif`) |
| `cycle_id` | String | Identifiant unique du cycle (timestamp ISO) |
| `anomaly_rate` | Float32 | Taux d'anomalies détectées (0.01.0) |
| `known_bot_rate` | Float32 | Taux de KNOWN_BOT dans le cycle |
| `legit_browser_rate` | Float32 | Taux de LEGITIMATE_BROWSER dans le cycle |
| `drift_rate` | Float32 | Fraction de features en drift (KS ou KL) |
| `corr_rate` | Float32 | Taux de sessions corrélées (correlated=1) |
| `cycle_latency_s` | Float32 | Durée totale du cycle d'inférence en secondes |
| `alert_flags` | Array(String) | Liste des alertes déclenchées dans ce cycle |
| `recorded_at` | DateTime | Horodatage d'enregistrement |
- **Moteur** : MergeTree
- **Partition** : `toDate(recorded_at)`
- **Tri** : `(recorded_at, model_name)`
- **TTL** : `recorded_at + INTERVAL 90 DAY`
---
### soc_feedback
Feedback des analystes SOC pour l'entraînement supervisé XGBoost et le MetaLearner.
| Colonne | Type | Description |
|---------|------|-------------|
| `src_ip` | IPv6 | Adresse IP classifiée |
| `label` | LowCardinality(String) | Classification : `bot`, `legitimate`, `suspicious` |
| `analyst` | String | Identifiant de l'analyste |
| `confidence` | Float32 | Confiance déclarée (0.01.0) |
| `notes` | String | Notes libres de l'analyste |
| `created_at` | DateTime | Horodatage de la classification |
- **Moteur** : ReplacingMergeTree(created_at)
- **Partition** : `toDate(created_at)`
- **Tri** : `(src_ip, created_at)`
---
### audit_logs
Journal d'audit SOC pour le suivi de l'activité du dashboard.
| Colonne | Type | Défaut | Description |
|---------|------|--------|-------------|
| `timestamp` | DateTime | `now()` | Horodatage de l'événement |
| `user_name` | LowCardinality(String) | `'soc_user'` | Nom de l'analyste |
| `action` | LowCardinality(String) | — | Action effectuée |
| `entity_type` | LowCardinality(String) | `''` | Type d'entité (ip, ja4, etc.) |
| `entity_id` | String | `''` | Identifiant de l'entité |
| `entity_count` | UInt32 | `0` | Nombre d'entités |
| `details` | String CODEC(ZSTD(3)) | `''` | Détails en JSON |
| `client_ip` | String | `''` | IP du client analyste |
- **Moteur** : MergeTree
- **Partition** : `toDate(timestamp)`
- **Tri** : `(timestamp, user_name, action)`
- **TTL** : `toDate(timestamp) + INTERVAL 90 DAY`
- **Settings** : `index_granularity = 8192`
---
## Vues matérialisées
### mv_http_logs (`ja4_logs`)
- **Source** : `ja4_logs.http_logs_raw`
- **Cible** : `ja4_logs.http_logs`
- **Transformation** : Parse le champ `raw_json` via les fonctions `JSONExtract*`.
Enrichit avec les données ASN depuis `dict_iplocate_asn` et la détection de bots
Anubis via `dict_anubis_ip` + `dict_anubis_asn` avec cascade de priorité
COALESCE : IP/CIDR → ASN.
### mv_agg_host_ip_ja4_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_host_ip_ja4_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip, ja4, host, src_asn)`.
Calcule ~50 features comportementales : compteurs de hits, ratios POST,
unicité des chemins/paramètres, jitter TCP, timing SYN, keep-alives,
compteurs d'orphelins, rotation UA, variance des métadonnées IP, etc.
### mv_agg_header_fingerprint_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_header_fingerprint_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip)`.
Calcule le hash d'ordre des en-têtes, le nombre d'en-têtes, le score de
conformité navigateur (Sec-CH-UA = 100, UA seul = 50), l'incohérence
UA↔Sec-CH-UA-Platform et Sec-CH-UA-Mobile.
### mv_agg_path_sequences_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_path_sequences_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip, ja4, host)`.
Stocke `groupArrayState(100)(tuple(timestamp, path))`.
### mv_agg_request_timing_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_request_timing_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip, ja4, host)`.
Stocke `groupArrayIfState(500)(a_timestamp, a_timestamp > 0)`.
### mv_agg_ip_behavior_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_ip_behavior_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip)`.
Stocke la séquence JA4, la distribution sumMap hôte→hits, le compteur
d'hôtes uniques, les hits totaux et les bornes temporelles.
### mv_agg_resource_cascade_1h (`ja4_processing`)
- **Source** : `ja4_logs.http_logs`
- **Cible** : `ja4_processing.agg_resource_cascade_1h`
- **Transformation** : GROUP BY `(toStartOfHour(time), src_ip, ja4, host)`.
Stocke `groupArrayState(200)(tuple(timestamp, is_asset))` où is_asset est
déterminé par correspondance regex sur les extensions de fichiers statiques.
---
## Dictionnaires
### Dictionnaires basés sur fichier CSV
Tous les fichiers doivent être placés dans `/var/lib/clickhouse/user_files/`.
| Dictionnaire | Fichier CSV | Clé | Layout | Attributs | Lifetime | Entrées approx. |
|--------------|------------|-----|--------|-----------|----------|------------------|
| `dict_iplocate_asn` | `iplocate-ip-to-asn.csv` | `network` (String) | IP_TRIE | `asn` (UInt32), `country_code`, `name` | 36007200 s | ~714K |
| `dict_bot_ip` | `bot_ip.csv` | `prefix` (String) | IP_TRIE | `bot_name` (String) | 300 s | ~3,5K CIDR |
| `dict_bot_ja4` | `bot_ja4.csv` | `ja4` (String) | COMPLEX_KEY_HASHED | `bot_name` (String) | 300 s | ~31 |
| `dict_browser_ja4` | `browser_ja4.csv` | `ja4` (String) | COMPLEX_KEY_HASHED | `browser_family`, `tls_library`, `context` | 300 s | ~1,2K |
| `dict_browser_h2` | `browser_h2.csv` | `h2_fingerprint` (String) | COMPLEX_KEY_HASHED | `browser_family`, `h2_known` (UInt8) | 300 s | ~50 |
| `dict_asn_reputation` | `asn_reputation.csv` | `src_asn` (UInt64) | HASHED | `label` (String) | 300 s | ~82K |
### Dictionnaires basés sur ClickHouse
| Dictionnaire | Table source | Clé | Layout | Attributs | Lifetime |
|--------------|-------------|-----|--------|-----------|----------|
| `dict_anubis_ip` | `ja4_processing.anubis_ip_rules` | `prefix` (String) | IP_TRIE | `bot_name`, `action`, `rule_id` (UInt64), `has_ua` (UInt8), `category` | 300600 s |
| `dict_anubis_asn` | `ja4_processing.anubis_asn_rules` | `asn` (UInt32) | FLAT | `bot_name`, `action`, `category` | 300600 s |
> **Note** : les dictionnaires Anubis basés sur ClickHouse nécessitent que les
> identifiants de connexion soient configurés dans les fichiers SQL (mot de passe
> par défaut `CHANGE_ME` à remplacer avant la mise en production).
---
## Vues
### view_ai_features_1h
Calcule ~85 features ML par `(src_ip, ja4, host)` sur les dernières 24 heures
en joignant `agg_host_ip_ja4_1h` et `agg_header_fingerprint_1h`.
| Catégorie | Features |
|-----------|----------|
| Comportementales | `hits`, `hit_velocity`, `fuzzing_index`, `post_ratio`, `orphan_ratio`, `asset_ratio`, `direct_access_ratio` |
| Connexion | `max_keepalives`, `multiplexing_efficiency`, `port_exhaustion_ratio`, `src_port_density` |
| Navigateur | `modern_browser_score`, `ua_ch_mismatch`, `header_order_shared_count`, `is_headless` |
| TLS | `alpn_http_mismatch`, `is_alpn_missing`, `sni_host_mismatch` |
| L4 | `tcp_jitter_variance`, `avg_ttl`, `ttl_std`, `syn_timing_cv`, `window_mss_ratio` |
| Réputation | `bot_name` (dict_bot_ip / dict_bot_ja4), `browser_family` (dict_browser_ja4), `asn_label` (dict_asn_reputation), `anubis_bot_name`/`action`/`category` (dict_anubis_ip / dict_anubis_asn) |
| Statistiques | `temporal_entropy`, `ja3_diversity_ratio`, `ja4_asn_concentration`, `ja4_country_concentration` |
| P1 | `has_xff`, `unusual_content_type_ratio`, `non_standard_port_ratio`, `login_post_concentration` |
Utilise des fonctions de fenêtrage (`sum() OVER`, `count() OVER`, `uniqExact() OVER`)
pour les features de concentration et de partage TCP.
### view_ip_recurrence
Agrège les données de récurrence depuis `ml_detected_anomalies` (30 derniers jours) :
```sql
SELECT
src_ip,
count() AS recurrence,
min(detected_at) AS first_seen,
max(detected_at) AS last_seen,
max(anomaly_score) AS worst_score,
argMax(threat_level, anomaly_score) AS worst_threat_level
FROM ja4_processing.ml_detected_anomalies
WHERE detected_at >= now() - INTERVAL 30 DAY
GROUP BY src_ip;
```
### view_form_bruteforce_detected
Détection de force brute sur les formulaires. Source : `agg_host_ip_ja4_1h`
(dernières 24h). Filtre les combinaisons `(src_ip, host)` ayant
`count_post >= 10`. Retourne `src_ip`, `host`, `ja4` (argMax par hits),
`hits`, `query_params_count`.
### view_host_ip_ja4_rotation
Détection de rotation d'empreintes JA4 par IP. Source : `agg_host_ip_ja4_1h`
(dernières 24h, ja4 ≠ ''). Filtre les IP ayant `distinct_ja4_count >= 2`.
Retourne `src_ip`, `distinct_ja4_count`, `total_hits`, `first_seen`, `last_seen`.
### view_dashboard_user_agents
Agrégation des User-Agents pour le dashboard. Source : `ja4_logs.http_logs`
(7 derniers jours). GROUP BY `(src_ip, ja4, toStartOfHour(time), log_date)`.
Retourne `src_ip` (normalisé IPv4), `ja4`, `hour`, `log_date`,
`user_agents` (groupUniqArray(100)), `requests`.
### view_dashboard_entities
Vue d'entités du dashboard. Source : `ja4_logs.http_logs` (7 derniers jours).
Structure `UNION ALL` de 5 branches — une par type d'entité : `ip`, `ja4`,
`country`, `asn`, `host`. Retourne `entity_type`, `entity_value`, `src_ip`,
`ja4`, `host`, `log_date`, `client_headers`, `asns`, `countries`, `user_agents`.
### view_resource_cascade_1h (thèse §5.4)
Analyse de cascade de chargement de ressources. Source : `agg_resource_cascade_1h`
(dernières 24h). Sépare les chargements en documents (is_asset=0) et assets
(is_asset=1). Calcule `doc_count`, `asset_count`, `root_to_first_asset_delay`,
`asset_load_stddev` (σ des timestamps d'assets — mesure de simultanéité).
### view_thesis_features_1h (thèse §5)
Vue unifiée des features avancées de détection de la thèse. Joint (via CTEs
sur les 24 dernières heures) :
| Source | Features calculées |
|--------|-------------------|
| `agg_path_sequences_1h` (§5.1) | `path_transition_entropy` (entropie de Shannon normalisée des transitions Markov-1) |
| `agg_request_timing_1h` (§5.3) | `cadence_cv`, `burst_ratio` (Δt<100ms), `pause_ratio` (Δt>5s), `lag1_autocorrelation`, `benford_deviation` (χ² vs loi de Benford) |
| `agg_ip_behavior_1h` (§5.5/§5.8) | `ja4_drift_ratio`, `ja4_distinct_in_session`, `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` |
| `view_resource_cascade_1h` (§5.4) | `doc_count`, `asset_count`, `root_to_first_asset_delay`, `asset_load_stddev` |
**Clés de jointure** : `(window_start, src_ip, ja4, host)` pour §5.1/§5.3/§5.4 ;
`(window_start, src_ip)` pour §5.5/§5.8.
---
## Index de performance (10_perf_indexes.sql)
Migration idempotente ajoutant des index secondaires et projections aux tables
existantes (les installations fraîches les ont déjà dans 04/05/06) :
| Table | Index / Projection ajouté |
|-------|--------------------------|
| `ml_detected_anomalies` | `idx_detected_at` (minmax), `idx_threat_level` (set(8)), `idx_bot_name` (bloom_filter) |
| `ml_all_scores` | `idx_detected_at` (minmax), `idx_threat_level` (set(8)) |
| `http_logs` | `idx_src_ip` (bloom_filter(0.01)), `idx_ja4` (bloom_filter(0.01)) |
| `agg_host_ip_ja4_1h` | `proj_by_ip` (projection ORDER BY src_ip, window_start, ja4, host) |
---
## Comptes utilisateurs
| Utilisateur | Permissions | Usage |
|-------------|------------|-------|
| `data_writer` | INSERT + SELECT sur `ja4_logs.http_logs_raw` | Service correlator |
| `analyst` | SELECT sur `ja4_logs.http_logs`, `ja4_processing.ml_detected_anomalies`, `ja4_processing.ml_all_scores`, `ja4_processing.view_ai_features_1h`, `ja4_processing.view_ip_recurrence`, `ja4_processing.audit_logs` | Dashboard / analystes SOC |
> **Sécurité** : les mots de passe par défaut sont `ChangeMe`. Remplacer par des
> mots de passe forts avant la mise en production. Stocker les identifiants dans
> un gestionnaire de secrets.