# 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.0–1.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.0–1.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` | 3600–7200 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` | 300–600 s | | `dict_anubis_asn` | `ja4_processing.anubis_asn_rules` | `asn` (UInt32) | FLAT | `bot_name`, `action`, `category` | 300–600 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.