Réécriture des 3 fichiers de documentation de la base de données ClickHouse : - docs/database/schema.md : couverture complète des 2 bases, 14+ tables, 7 dictionnaires, 8 MVs, 8 vues, TTL, partitions, moteurs et colonnes - docs/database/migrations.md : 13 fichiers SQL (ajout 10-12), prérequis mis à jour (ClickHouse 24.8+, 5 CSV), deploy_schema.sh, init-stack.sh, vérification et rollback complets - shared/clickhouse/README.md : référence rapide des 13 fichiers, deploy_schema.sh, patron double-base, prérequis Suppression des références obsolètes : dict_anubis_ua, dict_anubis_country, anubis_ua_rules, anubis_country_rules. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
29 KiB
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 | 14 | 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 |
| Dictionnaires | 7 | dict_iplocate_asn, dict_bot_ip, dict_bot_ja4, dict_browser_ja4, dict_asn_reputation, dict_anubis_ip, dict_anubis_asn |
| Vues matérialisées | 8 | 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 | 8 | 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 |
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 |
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_rulesetanubis_country_rulesainsi que les dictionnairesdict_anubis_uaetdict_anubis_countryont é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(partitiontoDate(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(partitiontoDate(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
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_jsonvia les fonctionsJSONExtract*. Enrichit avec les données ASN depuisdict_iplocate_asnet la détection de bots Anubis viadict_anubis_ip+dict_anubis_asnavec 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). StockegroupArrayState(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). StockegroupArrayIfState(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). StockegroupArrayState(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_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 ~65+ 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) :
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.