From c96c41fb45c0be535ac848f9d7a594b1fa7bc9a7 Mon Sep 17 00:00:00 2001 From: toto Date: Thu, 9 Apr 2026 22:04:58 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20r=C3=A9=C3=A9criture=20compl=C3=A8te=20?= =?UTF-8?q?de=20la=20documentation=20des=20services=20en=20fran=C3=A7ais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - bot-detector.md : architecture 11 modules, 77/65 features, ensemble triple voix (EIF+AE+XGBoost), browser 5 axes, HDBSCAN, toutes les variables d'environnement vérifiées depuis le code source - dashboard.md : corrigé stack (Jinja2+htmx, pas React+Vite), 14 pages + 35 API routes + health, dual-database, IPv4/IPv6 - python-ja4common.md : ajouté CLICKHOUSE_DB_PROCESSING/LOGS, schéma dual-database, note dashboard n'utilise pas ja4_common Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/services/bot-detector.md | 764 +++++++++++++++++++++++--------- docs/services/dashboard.md | 548 ++++++++++++----------- docs/shared/python-ja4common.md | 236 +++++++--- 3 files changed, 1006 insertions(+), 542 deletions(-) diff --git a/docs/services/bot-detector.md b/docs/services/bot-detector.md index bff979c..c2e6e3b 100644 --- a/docs/services/bot-detector.md +++ b/docs/services/bot-detector.md @@ -1,221 +1,549 @@ # Bot Detector -The bot-detector is a Python service that performs machine-learning anomaly detection on aggregated HTTP/TLS traffic features stored in ClickHouse. It runs on a continuous cycle (default: every 5 minutes), using Isolation Forest to identify suspicious traffic patterns, enriched with SHAP explainability, DBSCAN clustering, and Anubis bot-rule enrichment. +Service Python de détection d'anomalies par apprentissage automatique semi-supervisé +sur le trafic HTTP/TLS agrégé dans ClickHouse. Fonctionne en cycle continu +(par défaut toutes les 5 minutes) avec un **ensemble à triple voix** +(Extended Isolation Forest + Autoencoder + XGBoost), enrichi par l'explicabilité +SHAP, le clustering HDBSCAN et la détection multifactorielle des navigateurs. -## ML Algorithm +--- -### Isolation Forest (Semi-Supervised) +## Architecture des modules -The core algorithm is **Isolation Forest** (Liu, Ting & Zhou, 2008) — an unsupervised anomaly detection algorithm that isolates anomalies by randomly partitioning feature space. Anomalies require fewer partitions to isolate than normal points. - -The approach is **semi-supervised** because: -1. **Known bots** are identified a priori via reputation dictionaries (IP, JA4, ASN) -2. **Human baseline** is identified via ASN reputation labels (`asn_label = 'human'`) -3. The model trains **only on human-baseline traffic** (minimum 500 sessions required) -4. Unknown traffic is scored by deviation from the human profile - -### Two-Model Architecture - -| Model | Condition | Features | Data | -|-------|-----------|----------|------| -| **Complet** | `correlated = 1` | 35 | HTTP + TCP + TLS (full pipeline data) | -| **Applicatif** | `correlated = 0` | 31 | HTTP only (no TLS correlation available) | - -### Threat Levels - -| Score Range | Level | Interpretation | -|------------|-------|----------------| -| `< -0.30` | **CRITICAL** | Extremely anomalous behavior | -| `< -0.15` | **HIGH** | Strong anomaly signal | -| `< -0.05` | **MEDIUM** | Moderate anomaly | -| `≥ -0.05` | **LOW** | Slightly unusual | - -## Feature List - -### Common Features (31 — Applicatif model) - -#### HTTP Behavior - -| Feature | Description | -|---------|-------------| -| `hits` | Request count in the window | -| `hit_velocity` | Requests per second | -| `fuzzing_index` | Path/parameter diversity anomaly score | -| `post_ratio` | Fraction of POST requests | -| `port_exhaustion_ratio` | Fraction of distinct source ports / total | -| `orphan_ratio` | Requests without TLS correlation | -| `head_ratio` | Fraction of HEAD requests | -| `http10_ratio` | Fraction of HTTP/1.0 requests | -| `generic_accept_ratio` | Fraction of short Accept headers | -| `sec_fetch_absence_rate` | Fraction missing Sec-Fetch-Site | -| `missing_accept_enc_ratio` | Fraction missing Accept-Encoding | -| `http_scheme_ratio` | Fraction using HTTP (not HTTPS) | - -#### Connection Management - -| Feature | Description | -|---------|-------------| -| `max_keepalives` | Max requests on a single Keep-Alive connection | -| `tcp_shared_count` | TCP connections shared between sessions | -| `multiplexing_efficiency` | HTTP/2 multiplexing efficiency | - -#### Browser Fingerprint - -| Feature | Description | -|---------|-------------| -| `header_count` | HTTP headers sent | -| `has_accept_language` | Accept-Language header presence | -| `has_cookie` | Cookie header presence | -| `has_referer` | Referer header presence | -| `modern_browser_score` | Composite browser compliance score (0–100) | -| `ua_ch_mismatch` | User-Agent vs Client Hints inconsistency | -| `ip_id_zero_ratio` | IP packets with ID=0 (headless/minimal stack) | -| `header_order_shared_count` | IPs sharing same header order | -| `header_order_confidence` | Normalized entropy of header order | -| `distinct_header_orders` | Distinct header orderings per IP | -| `is_fake_navigation` | Sec-Fetch-Mode=navigate with non-document dest | - -#### Navigation Patterns - -| Feature | Description | -|---------|-------------| -| `request_size_variance` | Variance of request sizes | -| `mss_mobile_mismatch` | TCP MSS vs mobile profile inconsistency | -| `asset_ratio` | Static asset request fraction | -| `direct_access_ratio` | Direct accesses (no referer) | -| `is_ua_rotating` | User-Agent rotation detected (flag) | -| `distinct_ja4_count` | Distinct JA4 fingerprints per IP | -| `anomalous_payload_ratio` | Anomalous payload size fraction | - -#### Concentration & Rarity - -| Feature | Description | -|---------|-------------| -| `src_port_density` | Source port entropy | -| `ja4_asn_concentration` | JA4 concentration within ASN | -| `ja4_country_concentration` | JA4 concentration per country | -| `is_rare_ja4` | Rare JA4 fingerprint (< 100 total hits) | - -#### Temporal & Diversity - -| Feature | Description | -|---------|-------------| -| `temporal_entropy` | Temporal distribution entropy | -| `path_diversity_ratio` | URL path diversity | -| `url_depth_variance` | URL depth variance | -| `ja3_diversity_ratio` | JA3 diversity ratio per IP | - -### Additional TCP/TLS Features (Complet model only — 4 extra) - -| Feature | Description | -|---------|-------------| -| `tcp_jitter_variance` | TCP inter-packet jitter variance | -| `alpn_http_mismatch` | ALPN vs actual HTTP protocol mismatch | -| `is_alpn_missing` | ALPN absent in ClientHello | -| `sni_host_mismatch` | TLS SNI vs HTTP Host mismatch | - -### L4 Fingerprint Features (Complet model) - -| Feature | Description | -|---------|-------------| -| `avg_ttl` | Average IP TTL (OS fingerprint) | -| `ttl_std` | TTL standard deviation | -| `no_window_scale_ratio` | Fraction without TCP window scale | -| `syn_timing_cv` | SYN timing coefficient of variation | -| `tls12_ratio` | Fraction of TLS 1.2 connections | -| `ip_df_variance` | IP Don't-Fragment flag variance | - -## Detection Pipeline +Le service est découpé en **11 modules** organisés ainsi : ``` -1. Read view_ai_features_1h (last 24h) → DataFrame -2. Read view_ip_recurrence → recurrence map -3. Clean columns (fillna, astype) -4. Split by correlated=1 / correlated=0 -5. For each model (Complet, Applicatif): - a. A7: Validate features (exclude missing/constant) - b. Separate known bots → log as KNOWN_BOT - c. Filter human baseline (asn_label='human', min 500 sessions) - d. Load or train Isolation Forest model - e. A1: Check concept drift (KS test on features) - f. Score unknown traffic - g. A10: Normalize scores to [-1, 0] - h. A2: Compute adaptive threshold = min(percentile_5, ANOMALY_THRESHOLD) - i. A6: Apply recurrence weighting - j. Filter scores below threshold - k. A4: SHAP explainability (top 5 features) - l. A8: DBSCAN clustering (campaign detection) -6. Concatenate results, deduplicate by src_ip (keep lowest score) -7. A5: Deduplication with TTL (skip recently reported IPs) -8. Insert into ml_detected_anomalies + ml_all_scores +__main__.py Point d'entrée (python -m bot_detector) + └─ cycle.py Boucle principale : requête ClickHouse → pipeline → insertion + ├─ config.py Variables d'environnement, flags de disponibilité + ├─ log.py Journalisation structurée JSON (structlog + RotatingFileHandler) + ├─ infra.py Client ClickHouse (via ja4_common), health check HTTP, arrêt propre + ├─ preprocessing.py Nettoyage du DataFrame, imputation, listes de features + │ └─ browser.py Identification multifactorielle des navigateurs (5 axes) + ├─ pipeline.py Orchestration : filtrage → entraînement → scoring → fusion + │ ├─ models.py EIF, TrafficAutoEncoder (PyTorch), XGBoost + │ └─ scoring.py Normalisation, seuil adaptatif, SHAP, HDBSCAN, dérive + └─ (insère dans ml_all_scores + ml_detected_anomalies) ``` -## Concept Drift Detection (A1) +| Module | Lignes | Rôle | +|--------|--------|------| +| `config.py` | 154 | Toute la configuration via `os.getenv()`, flags de disponibilité des librairies | +| `log.py` | 65 | `log_info()`, `log_decision()`, `append_training_history()` — JSONL rotatif | +| `infra.py` | 89 | Client ClickHouse (délègue à `ja4_common`), `score_to_threat_level()`, serveur de santé en thread daemon | +| `browser.py` | 170 | Détection multifactorielle des navigateurs sur 5 axes pondérés | +| `scoring.py` | 279 | Normalisation, seuil adaptatif, SHAP top-3, HDBSCAN, détection de dérive | +| `models.py` | 478 | `TrafficAutoEncoder`, entraînement/chargement EIF, XGBoost, élagage de features | +| `preprocessing.py` | 117 | `preprocess_df()` — nettoyage, typage, imputation, listes `FEATURES` / `FEATURES_COMPLET` | +| `pipeline.py` | 378 | `run_semi_supervised_logic()` — orchestration complète d'un modèle | +| `cycle.py` | 371 | `fetch_and_analyze()` — boucle principale, feedback SOC, multiwindow | +| `__main__.py` | 41 | Point d'entrée, bannière de démarrage, boucle `while True` | +| `__init__.py` | 1 | Docstring du package | -Uses the **Kolmogorov-Smirnov test** to compare feature distributions between the current data and the training data. If the fraction of drifted features exceeds `DRIFT_THRESHOLD` (default: 0.30), the model is retrained. +--- -## SHAP Explainability (A4) +## Configuration -When enabled (`ENABLE_SHAP=true`), computes SHAP values for each detected anomaly using `shap.TreeExplainer`. The top 5 contributing features are stored in the `reason` field. +Toute la configuration est lue via `os.getenv()` dans `config.py`. Aucun fichier YAML ni pydantic-settings. -## DBSCAN Clustering (A8) +### Connexion ClickHouse -When enabled (`ENABLE_CLUSTERING=true`), applies DBSCAN on anomaly feature vectors to group related anomalies into campaigns. Each anomaly gets a `campaign_id` (-1 = no cluster). +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `CLICKHOUSE_HOST` | str | `clickhouse` | Nom d'hôte du serveur ClickHouse (via `ja4_common`) | +| `CLICKHOUSE_PORT` | int | `8123` | Port HTTP ClickHouse (via `ja4_common`) | +| `CLICKHOUSE_USER` | str | `admin` | Utilisateur ClickHouse (via `ja4_common`) | +| `CLICKHOUSE_PASSWORD` | str | `""` | Mot de passe ClickHouse (via `ja4_common`) | +| `CLICKHOUSE_DB_PROCESSING` | str | `ja4_processing` | Base de données ML/agrégations (fallback : `CLICKHOUSE_DB`) | +| `CLICKHOUSE_DB_LOGS` | str | `ja4_logs` | Base de données des logs HTTP | -## Anubis Bot-Rule Enrichment +### Modèle et entraînement -The `view_ai_features_1h` view enriches each IP with Anubis bot detection using a priority cascade: -1. **UA + IP combined** (same `rule_id`) — highest confidence -2. **UA only** (no IP requirement) -3. **IP only** (no UA requirement) -4. **ASN match** -5. **Country match** +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `N_ESTIMATORS` | int | `300` | Nombre d'arbres pour l'Extended Isolation Forest | +| `ISOLATION_CONTAMINATION` | float | `0.001` | Paramètre de contamination EIF (plage ]0, 0.5[) | +| `ANOMALY_THRESHOLD` | float | `-0.05` | Seuil de score brut pour la détection d'anomalie | +| `ANOMALY_PERCENTILE` | int | `5` | Percentile pour le seuil adaptatif | +| `MODEL_DIR` | str | `/var/lib/bot_detector` | Répertoire de persistance des modèles | +| `MODEL_HISTORY_COUNT` | int | `10` | Nombre de versions de modèle conservées | +| `RETRAIN_INTERVAL_HOURS` | int | `24` | Intervalle de réentraînement EIF (heures) | +| `DRIFT_THRESHOLD` | float | `0.30` | Seuil de dérive KS (fraction de features driftées) | +| `MIN_VALID_FEATURE_RATIO` | float | `0.50` | Ratio minimal de features valides pour entraîner | +| `PRUNE_VARIANCE_THRESHOLD` | float | `1e-6` | Seuil de variance pour l'élagage de features | +| `VAL_ANOMALY_GATE` | float | `0.20` | Garde-fou : taux maximum d'anomalies en validation | -## Environment Variables +### Autoencoder -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `CLICKHOUSE_HOST` | string | `clickhouse` | ClickHouse server hostname | -| `CLICKHOUSE_PORT` | int | `8123` | ClickHouse HTTP port | -| `CLICKHOUSE_DB` | string | `ja4_processing` | Database name | -| `CLICKHOUSE_USER` | string | `admin` | ClickHouse username | -| `CLICKHOUSE_PASSWORD` | string | `""` | ClickHouse password | -| `ISOLATION_CONTAMINATION` | float | `0.02` | Contamination parameter for Isolation Forest | -| `ANOMALY_THRESHOLD` | float | `-0.03` | Score threshold for anomaly detection | -| `ANOMALY_PERCENTILE` | int | `5` | Percentile for adaptive threshold (A2) | -| `CYCLE_INTERVAL_SEC` | int | `300` | Seconds between detection cycles | -| `MAX_CONSECUTIVE_FAILURES` | int | `3` | Max consecutive failures before exit | -| `BOT_DETECTOR_LOG` | string | `/var/log/bot_detector/decisions.jsonl` | Decision log file path | -| `LOG_BACKUP_COUNT` | int | `7` | Number of rotated log backups | -| `MODEL_DIR` | string | `/var/lib/bot_detector` | Model persistence directory | -| `RETRAIN_INTERVAL_HOURS` | int | `24` | Hours between model retraining | -| `MODEL_HISTORY_COUNT` | int | `10` | Number of model versions to keep | -| `DRIFT_THRESHOLD` | float | `0.30` | KS-test drift threshold (A1) | -| `ENABLE_MULTIWINDOW` | bool | `false` | Enable 24h multi-window analysis (A3) | -| `MULTIWINDOW_VIEW` | string | `view_ai_features_24h` | View for multi-window mode | -| `ENABLE_SHAP` | bool | `true` | Enable SHAP explainability (A4) | -| `DEDUP_TTL_MIN` | int | `60` | Deduplication TTL in minutes (A5) | -| `RECURRENCE_WEIGHT` | float | `0.005` | Recurrence score weighting factor (A6) | -| `MIN_VALID_FEATURE_RATIO` | float | `0.50` | Min valid feature ratio (A7) | -| `ENABLE_CLUSTERING` | bool | `true` | Enable DBSCAN clustering (A8) | -| `CLUSTERING_MIN_SAMPLES` | int | `3` | DBSCAN min samples per cluster | -| `HEALTH_PORT` | int | `8080` | Health check HTTP server port | +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `AE_WEIGHT` | float | `0.30` | Poids de l'Autoencoder dans le score combiné (plage ]0, 1[) | +| `AE_EPOCHS` | int | `50` | Nombre d'époques d'entraînement | +| `AE_LATENT_DIM` | int | `16` | Dimension de l'espace latent | +| `AE_LEARNING_RATE` | float | `1e-3` | Taux d'apprentissage Adam | -## Output Tables +### XGBoost + +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `XGB_WEIGHT` | float | `0.20` | Poids de XGBoost dans le score final (plage ]0, 1[) | +| `XGB_MIN_LABELS` | int | `100` | Nombre minimal de labels SOC pour activer XGBoost | +| `XGB_RETRAIN_INTERVAL_HOURS` | int | `168` | Intervalle de réentraînement XGBoost (7 jours) | + +### Détection navigateur + +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `BROWSER_CONFIDENCE_THRESHOLD` | float | `0.55` | Confiance minimale pour classifier `LEGITIMATE_BROWSER` | +| `BROWSER_COHORT_RATIO` | float | `0.70` | Si ≥ 70 % des sessions d'un JA4 sont navigateur → propagation | + +### Clustering et explicabilité + +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `ENABLE_CLUSTERING` | bool | `true` | Activer le clustering HDBSCAN | +| `CLUSTERING_MIN_SAMPLES` | int | `3` | Taille minimale de cluster | +| `ENABLE_SHAP` | bool | `true` | Activer l'explicabilité SHAP (requiert `shap` installé) | + +### Cycle et opérations + +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `CYCLE_INTERVAL_SEC` | int | `300` | Intervalle entre les cycles (secondes) | +| `MAX_CONSECUTIVE_FAILURES` | int | `3` | Échecs consécutifs avant `healthy=False` | +| `DEDUP_TTL_MIN` | int | `60` | TTL de déduplication inter-cycle (minutes) | +| `RECURRENCE_WEIGHT` | float | `0.005` | Poids de la récurrence dans le score brut | +| `ENABLE_MULTIWINDOW` | bool | `false` | Activer l'analyse multi-fenêtre 24h | +| `MULTIWINDOW_VIEW` | str | `view_ai_features_24h` | Vue ClickHouse pour le mode multi-fenêtre | +| `ENABLE_FEEDBACK` | bool | `true` | Activer l'intégration du feedback SOC | +| `FEEDBACK_WINDOW_DAYS` | int | `7` | Fenêtre de feedback SOC (jours) | + +### Journalisation et santé + +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `BOT_DETECTOR_LOG` | str | `/var/log/bot_detector/decisions.jsonl` | Chemin du fichier de décisions | +| `LOG_BACKUP_COUNT` | int | `7` | Nombre de fichiers de rotation conservés | +| `HEALTH_PORT` | int | `8080` | Port du serveur de santé HTTP | + +--- + +## Pipeline ML — Ensemble à triple voix + +### Vue d'ensemble + +Le bot-detector utilise trois modèles en parallèle, combinés par une pondération configurable : + +``` + ┌──────────────────────┐ + │ Extended Isolation │ + │ Forest (isotree) │──→ eif_norm (0–1) + └──────────────────────┘ │ + │ × (1 − AE_WEIGHT) + ┌──────────────────────┐ │ + │ TrafficAutoEncoder │ ├──→ combined_norm + │ (PyTorch) │──→ ae_norm (0–1) + └──────────────────────┘ × AE_WEIGHT + │ × (1 − XGB_WEIGHT) + ┌──────────────────────┐ │ + │ XGBoost │ ├──→ anomaly_score + │ (supervisé, labels │──→ xgb_prob (0–1) + │ SOC) │ × XGB_WEIGHT + └──────────────────────┘ +``` + +**Formule du score final :** + +``` +combined_norm = (1 − AE_WEIGHT) × eif_norm + AE_WEIGHT × ae_norm +anomaly_score = (1 − XGB_WEIGHT) × combined_norm + XGB_WEIGHT × xgb_prob +``` + +Avec les poids par défaut (`AE_WEIGHT=0.30`, `XGB_WEIGHT=0.20`) : + +``` +anomaly_score = 0.56 × eif_norm + 0.24 × ae_norm + 0.20 × xgb_prob +``` + +### Architecture duale + +Deux modèles indépendants tournent sur chaque cycle : + +| Modèle | Condition | Features | Données | +|--------|-----------|----------|---------| +| **Complet** | `correlated = 1` | 77 (`FEATURES_COMPLET`) | HTTP + TCP + TLS (L3→L7) | +| **Applicatif** | `correlated = 0` | 65 (`FEATURES`) | HTTP seul (L7 pur) | + +En mode multi-fenêtre (`ENABLE_MULTIWINDOW=true`), deux variantes supplémentaires sont exécutées sur la vue 24h : `Complet_24h` et `Applicatif_24h`. + +### Extended Isolation Forest (EIF) + +Modèle principal non supervisé. Utilise `isotree.IsolationForest` : + +```python +isotree.IsolationForest( + ntrees=300, # N_ESTIMATORS + ndim=min(3, n_features), + sample_size='auto', + missing_action='impute', + random_seed=42, + nthreads=-1, +) +``` + +**Fallback** si `isotree` n'est pas disponible : `sklearn.ensemble.IsolationForest(n_estimators=300, contamination=CONTAMINATION)`. + +**Calibration** : le score isotree brut (∈ [0, 1], >0.5 = anomalous) est converti en convention sklearn : `sklearn_equiv = 0.5 − isotree_score`. + +### TrafficAutoEncoder (PyTorch) + +Architecture symétrique encodeur-décodeur : + +``` +Encodeur : n_features → dim1 → dim2 → 16 (latent) +Décodeur : 16 → dim2 → dim1 → n_features + +dim1 = min(64, max(n_features, latent_dim + 4)) +dim2 = min(32, max(dim1 // 2, latent_dim + 2)) +``` + +- Activations : `ReLU` + `BatchNorm1d` sur les couches cachées, `Sigmoid` en sortie du décodeur +- Optimiseur : `Adam(lr=1e-3, weight_decay=1e-5)` +- Perte : `MSELoss` +- Entraînement : 50 époques, batch_size=256 +- Score : erreur de reconstruction MSE par échantillon +- Normalisation des entrées : min-max [0, 1] par feature + +### XGBoost (supervisé) + +Entraîné sur les labels issus du feedback SOC (table `soc_feedback`) : + +```python +xgb.XGBClassifier( + n_estimators=200, + max_depth=6, + learning_rate=0.1, + scale_pos_weight=auto, # max(1, n_neg / n_pos) + eval_metric='logloss', + tree_method='hist', + random_state=42, + n_jobs=-1, +) +``` + +- Labels positifs (bot) : `HIGH`, `CRITICAL`, `ANUBIS_DENY`, `KNOWN_BOT` +- Labels négatifs (légitime) : `NORMAL`, `LEGITIMATE_BROWSER` +- Activation requiert ≥ `XGB_MIN_LABELS` (100) labels +- Réentraînement tous les `XGB_RETRAIN_INTERVAL_HOURS` (168h = 7 jours) + +--- + +## Détection multifactorielle des navigateurs + +Module `browser.py` — classifie chaque session sur 5 axes pondérés : + +| Axe | Clé | Poids | Composantes | +|-----|-----|-------|-------------| +| **1 — JA4 connu** | `axis_ja4_known` | 0.25 | Famille navigateur identifiée dans `dict_browser_ja4` → 1.0, sinon 0.0 | +| **2 — Structure JA4** | `axis_ja4_struct` | 0.15 | TLS 1.3 (×0.35), h2/h3 (×0.25), nb ciphers 10–25 (×0.20), nb extensions 10–25 (×0.20) | +| **3 — HTTP moderne** | `axis_http_modern` | 0.25 | modern_browser_score ≥ 50 (×0.35), Accept-Language (×0.20), Sec-Fetch < 0.3 (×0.25), generic_accept < 0.3 (×0.10), pas de ua_ch_mismatch (×0.10) | +| **4 — Comportement navigation** | `axis_nav_behavior` | 0.15 | has_cookie (×0.25), has_referer (×0.25), asset_ratio > 0.15 (×0.25), direct_access < 0.5 (×0.25) | +| **5 — Cohérence TLS/TCP** | `axis_tls_coherence` | 0.20 | Pas d'alpn_mismatch (×0.25), window_scale OK (×0.20), tls12 < 0.1 (×0.20), pas d'http10 (×0.15), ALPN présent (×0.20) | + +**Seuil** : `browser_confidence ≥ 0.55` + famille identifiée → `LEGITIMATE_BROWSER` + +**Propagation par cohorte** : si ≥ 70 % des sessions partageant un JA4 sont classées navigateur, les sessions `NORMAL`/`LOW` restantes avec le même JA4 sont aussi classées `LEGITIMATE_BROWSER`. + +**Inférence de famille** : pour les JA4 inconnus, correspondance structurelle avec les profils `_BROWSER_JA4_PROFILES` (Chromium, Firefox, Safari, Tor_Browser) — requiert `browser_confidence ≥ 0.45`. + +--- + +## Scoring et normalisation + +### Normalisation des scores (`normalize_scores`) + +Les scores bruts (négatifs = anomalous) sont mappés vers **[0, 1]** avec **1 = le plus anomalous** : + +``` +result[mask] = clip(−scores / (−s_min + 1e-9), 0, 1) +``` + +Les scores ≥ 0 sont mis à 0. + +### Seuil adaptatif (`compute_adaptive_threshold`) + +``` +threshold = min(percentile(scores_négatifs, ANOMALY_PERCENTILE), ANOMALY_THRESHOLD) +``` + +Avec `ANOMALY_PERCENTILE=5` et `ANOMALY_THRESHOLD=-0.05`. + +### Pénalité de récurrence + +Appliquée au `raw_anomaly_score` : + +``` +raw_anomaly_score −= log1p(recurrence_count) × RECURRENCE_WEIGHT +``` + +### Niveaux de menace + +| Plage de score brut | Niveau | Interprétation | +|---------------------|--------|----------------| +| `< −0.30` | **CRITICAL** | Comportement extrêmement anomalous | +| `< −0.15` | **HIGH** | Signal d'anomalie fort | +| `< −0.05` | **MEDIUM** | Anomalie modérée | +| `< 0` | **LOW** | Légèrement inhabituel | +| `≥ 0` | **NORMAL** | Trafic normal | + +--- + +## Clustering HDBSCAN des campagnes + +Lorsque `ENABLE_CLUSTERING=true`, les anomalies sont regroupées en campagnes par HDBSCAN : + +```python +hdbscan.HDBSCAN( + min_cluster_size=CLUSTERING_MIN_SAMPLES, # défaut : 3 + min_samples=max(2, CLUSTERING_MIN_SAMPLES − 1), # défaut : 2 + cluster_selection_method='eom', +) +``` + +**Espace de clustering** : si un Autoencoder est disponible, le clustering s'effectue dans l'**espace latent 16-dim** de l'AE. Sinon, `StandardScaler` est appliqué sur les features brutes. + +**Fallback** si `hdbscan` n'est pas disponible : `DBSCAN(eps=0.5, min_samples=CLUSTERING_MIN_SAMPLES)`. + +Chaque anomalie reçoit un `campaign_id` (−1 = pas de cluster). + +--- + +## Liste des features + +### Features communes — modèle Applicatif (65 features) + +#### Comportement HTTP + +| Feature | Description | +|---------|-------------| +| `hits` | Nombre de requêtes dans la fenêtre | +| `hit_velocity` | Requêtes par seconde | +| `fuzzing_index` | Score de diversité chemins/paramètres | +| `post_ratio` | Fraction de requêtes POST | +| `port_exhaustion_ratio` | Fraction de ports source distincts / total | +| `orphan_ratio` | Requêtes sans corrélation TLS | +| `head_ratio` | Fraction de requêtes HEAD | +| `http10_ratio` | Fraction de requêtes HTTP/1.0 | +| `generic_accept_ratio` | Fraction d'en-têtes Accept courts | +| `sec_fetch_absence_rate` | Fraction sans Sec-Fetch-Site | +| `missing_accept_enc_ratio` | Fraction sans Accept-Encoding | +| `http_scheme_ratio` | Fraction utilisant HTTP (pas HTTPS) | + +#### Gestion de connexion + +| Feature | Description | +|---------|-------------| +| `max_keepalives` | Max de requêtes sur une seule connexion Keep-Alive | +| `tcp_shared_count` | Connexions TCP partagées entre sessions | +| `multiplexing_efficiency` | Efficacité du multiplexage HTTP/2 | + +#### Empreinte navigateur + +| Feature | Description | +|---------|-------------| +| `header_count` | Nombre d'en-têtes HTTP envoyés | +| `has_accept_language` | Présence de l'en-tête Accept-Language | +| `has_cookie` | Présence de l'en-tête Cookie | +| `has_referer` | Présence de l'en-tête Referer | +| `modern_browser_score` | Score composite de conformité navigateur (0–100) | +| `ua_ch_mismatch` | Incohérence User-Agent vs Client Hints | +| `ip_id_zero_ratio` | Paquets IP avec ID=0 (pile minimaliste/headless) | +| `header_order_shared_count` | IPs partageant le même ordre d'en-têtes | +| `header_order_confidence` | Entropie normalisée de l'ordre des en-têtes | +| `distinct_header_orders` | Ordres d'en-têtes distincts par IP | +| `is_fake_navigation` | Sec-Fetch-Mode=navigate avec destination non-document | + +#### Comportement de navigation + +| Feature | Description | +|---------|-------------| +| `request_size_variance` | Variance de la taille des requêtes | +| `mss_mobile_mismatch` | Incohérence TCP MSS vs profil mobile | +| `asset_ratio` | Fraction de requêtes de ressources statiques | +| `direct_access_ratio` | Accès directs (sans referer) | +| `is_ua_rotating` | Rotation de User-Agent détectée | +| `distinct_ja4_count` | Empreintes JA4 distinctes par IP | +| `anomalous_payload_ratio` | Fraction de charges utiles anomalous | + +#### Concentration et rareté + +| Feature | Description | +|---------|-------------| +| `src_port_density` | Entropie des ports source | +| `ja4_asn_concentration` | Concentration JA4 au sein de l'ASN | +| `ja4_country_concentration` | Concentration JA4 par pays | +| `is_rare_ja4` | Empreinte JA4 rare (< 100 hits totaux) | + +#### Temporel et diversité + +| Feature | Description | +|---------|-------------| +| `temporal_entropy` | Entropie de la distribution temporelle | +| `path_diversity_ratio` | Diversité des chemins URL | +| `url_depth_variance` | Variance de la profondeur des URL | + +#### Anubis + +| Feature | Description | +|---------|-------------| +| `anubis_is_flagged` | Signal de suspicion Anubis (bot détecté, action ni ALLOW/DENY/vide) | + +#### Navigateur multifactoriel + +| Feature | Description | +|---------|-------------| +| `is_known_browser` | JA4 correspond à un navigateur connu | +| `browser_consistency_score` | Score composite de cohérence navigateur | +| `browser_confidence` | Confiance globale de l'identification navigateur | +| `axis_ja4_known` | Score de l'axe 1 (JA4 connu) | +| `axis_ja4_struct` | Score de l'axe 2 (structure JA4) | +| `axis_http_modern` | Score de l'axe 3 (HTTP moderne) | +| `axis_nav_behavior` | Score de l'axe 4 (comportement navigation) | +| `axis_tls_coherence` | Score de l'axe 5 (cohérence TLS/TCP) | + +#### Thèse §5 — Features avancées + +| Feature | Description | +|---------|-------------| +| `path_transition_entropy` | Entropie des transitions de chemins | +| `cadence_cv` | Coefficient de variation de la cadence de requêtes | +| `burst_ratio` | Fraction de requêtes en rafale | +| `pause_ratio` | Fraction de pauses longues | +| `lag1_autocorrelation` | Autocorrélation lag-1 des inter-arrivées | +| `benford_deviation` | Déviation par rapport à la loi de Benford | +| `host_diversity` | Diversité des hôtes ciblés | +| `host_sweep_speed` | Vitesse de balayage des hôtes | +| `host_coverage_uniformity` | Uniformité de couverture des hôtes | + +#### Features TCP fenêtre + +| Feature | Description | +|---------|-------------| +| `true_window_size` | Taille réelle de la fenêtre TCP | +| `window_mss_ratio` | Ratio fenêtre TCP / MSS | + +#### Features de détection avancées + +| Feature | Description | +|---------|-------------| +| `has_xff` | Présence de l'en-tête X-Forwarded-For | +| `unusual_content_type_ratio` | Fraction de Content-Type inhabituels | +| `non_standard_port_ratio` | Fraction de ports non standards | +| `login_post_concentration` | Concentration de POST sur les pages de login | +| `sec_ch_mobile_mismatch` | Incohérence Sec-CH-UA-Mobile | + +### Features supplémentaires — modèle Complet (+12 features TCP/TLS) + +| Feature | Description | +|---------|-------------| +| `tcp_jitter_variance` | Variance du jitter TCP inter-paquets | +| `alpn_http_mismatch` | Incohérence ALPN vs protocole HTTP réel | +| `is_alpn_missing` | ALPN absent dans le ClientHello | +| `sni_host_mismatch` | Incohérence TLS SNI vs en-tête Host HTTP | +| `ja3_diversity_ratio` | Ratio de diversité JA3 par IP | +| `syn_timing_cv` | Coefficient de variation du timing SYN | +| `tls12_ratio` | Fraction de connexions TLS 1.2 | +| `ip_df_variance` | Variance du flag Don't-Fragment IP | +| `avg_ttl` | TTL IP moyen (empreinte OS) | +| `ttl_std` | Écart-type du TTL | +| `no_window_scale_ratio` | Fraction sans TCP Window Scale | +| `ja4_drift_ratio` | Dérive JA4 intra-session (§5.5) | + +--- + +## Pipeline de détection + +``` +1. Requête view_ai_features_1h → DataFrame +2. Enrichissement optionnel view_thesis_features_1h (features thèse §5) +3. Prétraitement : preprocess_df() (nettoyage, browser axes, imputation) +4. Chargement du feedback SOC → reclassification +5. Chargement de la carte de récurrence (view_ip_recurrence) +6. Séparation par correlated = 1 / correlated = 0 +7. Pour chaque modèle (Complet, Applicatif) : + a. Validation des features (exclure constantes/manquantes) + b. Séparation des bots connus → journalisation KNOWN_BOT + c. Filtrage de la baseline humaine (asn_label = 'human') + d. Chargement ou entraînement EIF + AE + e. Scoring du trafic inconnu (EIF + AE) + f. Chargement ou entraînement XGBoost (si labels disponibles) + g. Combinaison des scores (formule triple voix) + h. Normalisation [0, 1] + i. Seuil adaptatif + j. Pénalité de récurrence + k. SHAP (top-3 features) + l. HDBSCAN clustering → campaign_id + m. Détection de dérive (KS test) +8. Mode multi-fenêtre (si activé) : idem sur view_ai_features_24h +9. Insertion → ml_all_scores (toutes les sessions scorées) +10. Déduplication intra-cycle (garder raw_anomaly_score le plus bas par IP) +11. Déduplication inter-cycle (TTL, skip si détecté récemment sauf aggravation ≥ 0.05) +12. Insertion → ml_detected_anomalies (anomalies filtrées) +``` + +--- + +## Détection de dérive (Kolmogorov-Smirnov) + +Par feature, un **test KS bilatéral** compare la distribution courante avec la distribution d'entraînement (reconstruite par interpolation à partir d'un digest quantile p10/p25/p50/p75/p90) : + +- Feature driftée si `p_value < 0.05` +- Dérive globale = fraction de features driftées +- Si `drift > DRIFT_THRESHOLD` (0.30) → réentraînement automatique + +**Fallback** (sans `scipy`) : méthode Z-score — feature driftée si `|moyenne_courante − moyenne_entraînement| / std_entraînement > 2.0`. + +--- + +## Explicabilité SHAP + +Lorsque `ENABLE_SHAP=true` et que la librairie `shap` est disponible : + +- Calcul des valeurs SHAP via `TreeExplainer` sur le modèle EIF +- Les **3 features les plus contributives** sont stockées dans le champ `reason` +- Format : `"feature1=valeur (±shap), feature2=valeur (±shap), feature3=valeur (±shap)"` + +--- + +## Enrichissement Anubis + +La vue `view_ai_features_1h` enrichit chaque IP via les dictionnaires Anubis selon une cascade de priorité : + +1. **UA + IP combinés** (même `rule_id`) — confiance maximale +2. **UA seul** (pas de condition IP) +3. **IP seul** (pas de condition UA) +4. **Correspondance ASN** +5. **Correspondance pays** + +--- + +## Tables de sortie ### ml_detected_anomalies -Anomaly detections above the threat threshold. Engine: `ReplacingMergeTree(detected_at)`, ORDER BY `(src_ip)`, TTL 30 days. +Détections d'anomalies au-dessus du seuil de menace. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(src_ip, model_name)`, TTL 7 jours. -Key columns: `detected_at`, `src_ip`, `ja4`, `host`, `bot_name`, `anomaly_score`, `raw_anomaly_score`, `threat_level`, `model_name`, `recurrence`, `campaign_id`, `reason`, `anubis_bot_name`, `anubis_bot_action`, `anubis_bot_category`, plus all ML features. +Colonnes clés : `detected_at`, `src_ip`, `ja4`, `host`, `bot_name`, `anomaly_score`, `raw_anomaly_score`, `threat_level`, `model_name`, `recurrence`, `campaign_id`, `reason`, `anubis_bot_name`, `anubis_bot_action`, `anubis_bot_category`, plus toutes les features ML. ### ml_all_scores -All classifications (no threshold filter) for observability. Engine: `ReplacingMergeTree(detected_at)`, ORDER BY `(window_start, src_ip, ja4, host, model_name)`, TTL 3 days. +Toutes les classifications (sans filtre de seuil) pour l'observabilité. Engine : `ReplacingMergeTree(detected_at)`, ORDER BY `(window_start, src_ip, ja4, host, model_name)`, TTL 7 jours. -## Decision Log Format +--- -The `decisions.jsonl` file contains structured JSONL entries: +## Format du journal de décisions + +Le fichier `decisions.jsonl` contient des entrées JSONL structurées : ```json {"event": "CYCLE_START", "cycle_id": "20260309T143000", "total": 5000, "human": 1500, "known_bot": 200, "correlated": 3000} @@ -224,42 +552,52 @@ The `decisions.jsonl` file contains structured JSONL entries: {"event": "CYCLE_END", "cycle_id": "20260309T143000", "anomalies": 15, "known_bots": 200, "duration_sec": 12.5} ``` -Log rotation: 50 MB max size × `LOG_BACKUP_COUNT` backups (default 7). +Rotation des logs : 50 Mo max × `LOG_BACKUP_COUNT` sauvegardes (défaut : 7). -## Health Check Endpoint +--- -- **URL**: `GET http://localhost:8080/` -- **Response**: `200 OK` with status JSON -- Runs in a separate thread +## Point de santé -## Model Persistence +- **URL** : `GET http://localhost:8080/` +- **Réponses** : `200 OK` (corps `OK`) ou `503 Service Unavailable` (corps `DEGRADED`) +- Exécuté dans un thread daemon au démarrage +- État mis à `False` après `MAX_CONSECUTIVE_FAILURES` (3) échecs consécutifs ClickHouse, remis à `True` dès le premier succès -| File | Description | -|------|-------------| -| `model__.joblib` | Serialized Isolation Forest (joblib) | -| `model__.meta.json` | Model metadata (features, thresholds, training stats) | -| `model_.current` | Pointer to active model version | -| `training_history.jsonl` | Training history log | +--- -Models are rotated: only the last `MODEL_HISTORY_COUNT` versions (default 10) are kept. +## Persistance des modèles -## Docker Deployment +| Fichier | Description | +|---------|-------------| +| `model__.joblib` | EIF sérialisé (joblib) | +| `model__.meta.json` | Métadonnées (features, seuils, statistiques d'entraînement, digest quantile) | +| `model_.current` | Pointeur vers la version active | +| `training_history.jsonl` | Historique d'entraînement | + +Rotation automatique : seules les `MODEL_HISTORY_COUNT` dernières versions (défaut : 10) sont conservées. + +--- + +## Déploiement Docker ```bash -# Build +# Construction de l'image make build-bot-detector -# Run with docker-compose +# Exécution avec docker-compose cd services/bot-detector docker-compose up -d + +# Tests +make test-bot-detector ``` ### Volumes -| Host Path | Container Path | Description | -|-----------|---------------|-------------| -| `./bot_detector_logs` | `/var/log/bot_detector` | Decision logs (JSONL) | -| `./bot_detector_models` | `/var/lib/bot_detector` | Persisted ML models | -| `./reputation/data/user_files/bot_ip.csv` | `/data/bot_ip.csv` (ro) | Known bot IP list | -| `./reputation/data/user_files/bot_ja4.csv` | `/data/bot_ja4.csv` (ro) | Known bot JA4 list | -| `./reputation/data/user_files/asn_reputation.csv` | `/data/asn_reputation.csv` (ro) | ASN reputation labels | +| Chemin hôte | Chemin conteneur | Description | +|-------------|-----------------|-------------| +| `./bot_detector_logs` | `/var/log/bot_detector` | Journaux de décisions (JSONL) | +| `./bot_detector_models` | `/var/lib/bot_detector` | Modèles ML persistés | +| `./reputation/data/user_files/bot_ip.csv` | `/data/bot_ip.csv` (ro) | Liste d'IPs de bots connus | +| `./reputation/data/user_files/bot_ja4.csv` | `/data/bot_ja4.csv` (ro) | Liste de JA4 de bots connus | +| `./reputation/data/user_files/asn_reputation.csv` | `/data/asn_reputation.csv` (ro) | Labels de réputation ASN | diff --git a/docs/services/dashboard.md b/docs/services/dashboard.md index 75c6af2..d53c568 100644 --- a/docs/services/dashboard.md +++ b/docs/services/dashboard.md @@ -1,307 +1,343 @@ # Dashboard -The dashboard is a SOC (Security Operations Center) web application built with FastAPI (backend) and React (frontend) that provides real-time visualization, investigation, and analysis of bot detections generated by the [bot-detector](bot-detector.md). It queries ClickHouse (`ja4_processing`) for all data. +Application web SOC (Security Operations Center) construite avec **FastAPI + Jinja2 + htmx**, +offrant la visualisation en temps réel, l'investigation et l'analyse des détections de bots +générées par le [bot-detector](bot-detector.md). Interroge ClickHouse sur deux bases de données +(`ja4_processing` et `ja4_logs`). -## Technology Stack +--- -| Component | Technology | -|-----------|-----------| +## Pile technologique + +| Composant | Technologie | +|-----------|-------------| | Backend | Python 3.11 + FastAPI | -| Frontend | React + Vite | -| Database | ClickHouse (via `ja4_common` shared client) | -| API Docs | Swagger UI (`/docs`) and ReDoc (`/redoc`) | +| Templates | Jinja2 (rendu côté serveur) | +| Interactions dynamiques | htmx (mises à jour partielles via JSON API) | +| Graphiques | Chart.js + ECharts | +| Style | Tailwind CSS (CDN) | +| Base de données | ClickHouse via `clickhouse-connect` (client propre, **PAS** `ja4_common`) | +| Documentation API | Swagger UI (`/docs`) + OpenAPI JSON (`/openapi.json`) | + +> **Note :** le dashboard n'utilise **pas** la bibliothèque partagée `ja4_common`. Il possède +> son propre client ClickHouse léger dans `backend/database.py`. + +--- + +## Structure des fichiers + +``` +services/dashboard/ +├── backend/ +│ ├── __init__.py +│ ├── main.py Application FastAPI, montage templates + static, CORS, health +│ ├── config.py Variables d'environnement, safe_identifier() +│ ├── database.py Client clickhouse-connect singleton, query(), query_scalar(), execute() +│ └── routes/ +│ ├── __init__.py +│ ├── api.py 35 routes JSON (1635 lignes) +│ └── pages.py 14 routes de pages HTML (83 lignes) +├── templates/ +│ ├── base.html Template de base (layout, navigation, Tailwind CDN) +│ ├── overview.html Vue d'ensemble du dashboard +│ ├── detections.html Tableau paginé des détections d'anomalies +│ ├── scores.html Tableau paginé de tous les scores ML +│ ├── traffic.html Tableau paginé du trafic HTTP brut +│ ├── ip_detail.html Investigation détaillée d'une IP +│ ├── ja4_detail.html Investigation détaillée d'une empreinte JA4 +│ ├── cluster_detail.html Investigation détaillée d'un cluster/campagne +│ ├── campaigns.html Vue des campagnes HDBSCAN +│ ├── features.html Analyse des features ML +│ ├── models.html Métadonnées et performances des modèles +│ ├── classify.html Interface de classification SOC +│ ├── tactics.html Tactiques de détection (brute-force, rotation, récurrence) +│ ├── reflists.html Listes de référence et dictionnaires ClickHouse +│ └── network.html Graphe réseau des campagnes +└── static/ Fichiers statiques (JS, CSS) +``` + +--- ## Configuration -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `CLICKHOUSE_HOST` | string | `clickhouse` | ClickHouse hostname | -| `CLICKHOUSE_PORT` | int | `8123` | ClickHouse HTTP port | -| `CLICKHOUSE_DB` | string | `ja4_processing` | Database name | -| `CLICKHOUSE_USER` | string | `admin` | ClickHouse user | -| `CLICKHOUSE_PASSWORD` | string | `""` | ClickHouse password | -| `API_HOST` | string | `0.0.0.0` | API listen address | -| `API_PORT` | int | `8000` | API listen port | -| `CORS_ORIGINS` | list | `["http://localhost:3000", "http://127.0.0.1:3000"]` | Allowed CORS origins | +Toute la configuration est lue via `os.getenv()` dans `backend/config.py`. -## API Reference +| Variable | Type | Défaut | Description | +|----------|------|--------|-------------| +| `CLICKHOUSE_HOST` | str | `localhost` | Nom d'hôte du serveur ClickHouse | +| `CLICKHOUSE_PORT` | int | `8123` | Port HTTP ClickHouse | +| `CLICKHOUSE_USER` | str | `default` | Utilisateur ClickHouse | +| `CLICKHOUSE_PASSWORD` | str | `""` | Mot de passe ClickHouse | +| `CLICKHOUSE_DB_PROCESSING` | str | `ja4_processing` | Base de données ML/agrégations (fallback : `CLICKHOUSE_DB`) | +| `CLICKHOUSE_DB_LOGS` | str | `ja4_logs` | Base de données des logs HTTP | +| `API_HOST` | str | `0.0.0.0` | Adresse d'écoute du serveur | +| `API_PORT` | int | `8000` | Port d'écoute du serveur | -All endpoints are prefixed with `/api/`. The dashboard exposes **74+ endpoints** across 20 routers. +### Schéma dual-database -### Health +Le dashboard utilise deux bases de données ClickHouse : -| Method | Path | Description | -|--------|------|-------------| -| GET | `/health` | Health check — returns ClickHouse connection status | +- **`CLICKHOUSE_DB_PROCESSING`** (`ja4_processing`) : tables ML, agrégations, dictionnaires, feedback SOC +- **`CLICKHOUSE_DB_LOGS`** (`ja4_logs`) : `http_logs`, trafic brut + +Les noms de bases sont validés par `safe_identifier()` (regex alphanumérique + underscore) avant injection dans les requêtes SQL. + +### Dualité IPv4/IPv6 + +- `http_logs.src_ip` est de type `IPv4` +- Les tables ML (`ml_all_scores`, `ml_detected_anomalies`) stockent `IPv6` (mappé `::ffff:x.x.x.x`) +- Utiliser `toIPv6()` pour les requêtes sur les tables ML +- Utiliser `toIPv4OrZero()` pour les requêtes sur `http_logs` +- Retirer le préfixe `::ffff:` à l'affichage --- -### Metrics (`/api/metrics`) +## Couche base de données (`backend/database.py`) -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/metrics` | Global dashboard metrics: detection counts by threat level, unique IPs, time series | -| GET | `/api/metrics/threats` | Threat distribution summary | -| GET | `/api/metrics/baseline` | Human baseline statistics | +Client `clickhouse-connect` avec singleton paresseux : + +| Fonction | Signature | Description | +|----------|-----------|-------------| +| `get_client()` | `→ Client` | Singleton `clickhouse_connect.get_client()` | +| `query(sql, params)` | `→ list[dict]` | Exécute un SELECT, retourne une liste de dicts | +| `query_scalar(sql, params)` | `→ Any` | Exécute un SELECT, retourne une valeur scalaire | +| `execute(sql, params)` | `→ None` | Exécute un DDL/DML sans retour | + +Conversion automatique des types : `IPv4Address`, `IPv6Address`, `bytes` → chaînes JSON-friendly. + +### Patron de requêtes paramétrées + +```python +from backend.config import DB_PROCESSING, DB_LOGS, safe_identifier +from backend.database import query, query_scalar + +_DB = safe_identifier(DB_PROCESSING) +rows = query( + f"SELECT src_ip, anomaly_score FROM {_DB}.ml_detected_anomalies " + "WHERE src_ip = toIPv6({ip:String}) ORDER BY detected_at DESC LIMIT 10", + {"ip": ip_value}, +) +``` --- -### Detections (`/api/detections`) +## Routes de pages (14) -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/detections` | Paginated detection list with filtering, sorting, and text search | -| GET | `/api/detections/{detection_id}` | Single detection details | +Toutes les pages sont rendues côté serveur via Jinja2 et utilisent htmx pour les mises à jour dynamiques. -**Query Parameters** (GET `/api/detections`): +| # | Chemin | Template | Description | +|---|--------|----------|-------------| +| 1 | `/` | `overview.html` | Vue d'ensemble : statistiques 24h, distribution des menaces, top IPs, timeline | +| 2 | `/detections` | `detections.html` | Tableau paginé des détections d'anomalies avec filtres | +| 3 | `/scores` | `scores.html` | Tableau paginé de tous les scores ML avec filtres | +| 4 | `/traffic` | `traffic.html` | Tableau paginé des logs HTTP bruts avec filtres | +| 5 | `/ip/{ip}` | `ip_detail.html` | Investigation détaillée d'une IP (détections, scores, logs, features, récurrence) | +| 6 | `/classify` | `classify.html` | Interface de classification SOC (feedback analyste) | +| 7 | `/features` | `features.html` | Analyse des features ML (profils humain/bot, importance) | +| 8 | `/models` | `models.html` | Métadonnées des modèles, statistiques de scoring | +| 9 | `/network` | `network.html` | Graphe réseau des campagnes (IP ↔ JA4 partagés) | +| 10 | `/campaigns` | `campaigns.html` | Campagnes HDBSCAN (clusters de bots agrégés) | +| 11 | `/ja4/{fingerprint:path}` | `ja4_detail.html` | Investigation détaillée d'une empreinte JA4 | +| 12 | `/cluster/{cid}` | `cluster_detail.html` | Investigation détaillée d'un cluster | +| 13 | `/tactics` | `tactics.html` | Tactiques de détection (brute-force, rotation JA4/UA, récurrence) | +| 14 | `/reflists` | `reflists.html` | Listes de référence et dictionnaires ClickHouse | -| Parameter | Type | Description | +--- + +## Routes API (35) + +Toutes les routes sont préfixées par `/api` et retournent du JSON. Utilisées par htmx depuis les templates et consommables par des clients externes. + +### Vue d'ensemble et statistiques + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 1 | GET | `/api/overview` | Résumé du dashboard : détections/scores/trafic 24h, distribution des menaces, top IPs, timeline, modèles, statistiques navigateur | +| 2 | GET | `/api/heatmap` | Heatmap temporelle (heure × jour de semaine) depuis `http_logs` | +| 3 | GET | `/api/geo` | Répartition géographique et ASN des sessions | +| 4 | GET | `/api/alerts` | Flux d'alertes en direct (détections récentes HIGH/CRITICAL) | +| 5 | GET | `/api/timeline-detail` | Détail horaire de la timeline par niveau de menace | + +### Détections et scores + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 6 | GET | `/api/detections` | Liste paginée des détections d'anomalies avec filtres (threat_level, search, asn, country, ja4, bot, browser) et tri | +| 7 | GET | `/api/scores` | Liste paginée des scores ML avec filtres similaires | +| 8 | GET | `/api/traffic` | Liste paginée des logs HTTP bruts avec filtres | + +### Investigation IP + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 9 | GET | `/api/ip/{ip}` | Détail IP : détections, scores, logs HTTP, features AI, récurrence | +| 10 | GET | `/api/ip/{ip}/radar` | Données pour graphique radar : IP vs baseline ISP vs baseline bot | + +### Features et modèles + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 11 | GET | `/api/features` | Statistiques des features AI/thèse, profils humain/bot, importance (variance) | +| 12 | GET | `/api/models` | Métadonnées des modèles depuis fichiers JSON + statistiques de scoring ClickHouse | +| 13 | GET | `/api/models/timeline` | Volume horaire de scoring par modèle (7 jours) | +| 14 | GET | `/api/models/threats` | Répartition des niveaux de menace par modèle | + +### Empreintes et navigateurs + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 15 | GET | `/api/fingerprints` | Analyse des empreintes JA4 avec mapping de bots | +| 16 | GET | `/api/browsers` | Distribution des familles de navigateurs via JA4 | +| 17 | GET | `/api/behavior` | Données scatter des features comportementales + distributions | + +### Classification SOC + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 18 | GET | `/api/classify/stats` | Résumé statistique des classifications SOC | +| 19 | GET | `/api/classify/suggested` | Top des IPs non classifiées triées par sévérité | +| 20 | POST | `/api/classify` | Soumettre un feedback analyste SOC (bot/legitimate/suspicious) | +| 21 | GET | `/api/classifications` | Entrées de feedback SOC récentes | + +### Campagnes et clusters + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 22 | GET | `/api/campaigns` | Clusters de campagnes HDBSCAN (agrégés) | +| 23 | GET | `/api/campaigns/graph` | Données de graphe réseau (nœuds IP + arêtes JA4 partagés) | +| 24 | GET | `/api/campaigns/scatter` | Scatter plot : score d'anomalie vs vélocité par IP et campagne | +| 25 | GET | `/api/campaigns/{cid}` | Détail campagne : membres, profil, timeline | + +### Investigation JA4 et cluster + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 26 | GET | `/api/ja4/{fingerprint:path}` | Investigation d'empreinte JA4 (IPs, scores, logs, features) | +| 27 | GET | `/api/cluster/{cid}` | Investigation enrichie de cluster (profil, membres, graphe, répartitions) | + +### Tactiques de détection + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 28 | GET | `/api/brute-force` | Détection de brute-force / credential stuffing | +| 29 | GET | `/api/ja4-rotation` | Détection de rotation d'empreintes JA4 (évasion) | +| 30 | GET | `/api/recurrence` | IPs de menaces persistantes/récurrentes | +| 31 | GET | `/api/cascade/{ip}` | Cascade de ressources pour détection de navigateur headless | +| 32 | GET | `/api/ua-rotation` | Détection de rotation de User-Agent | + +### Dictionnaires et listes de référence + +| # | Méthode | Chemin | Description | +|---|---------|--------|-------------| +| 33 | GET | `/api/dictionaries` | Métadonnées des dictionnaires ClickHouse | +| 34 | GET | `/api/reflist/{name}` | Contenu paginé d'une liste de référence / dictionnaire | +| 35 | GET | `/api/reflist/{name}/stats` | Statistiques agrégées d'une liste de référence | + +### Autres routes + +| Méthode | Chemin | Description | +|---------|--------|-------------| +| GET | `/health` | Point de santé — retourne l'état de connexion ClickHouse | +| — | `/static` | Montage de fichiers statiques (`backend/static/`) | +| GET | `/docs` | Documentation Swagger UI (auto-généré par FastAPI) | +| GET | `/openapi.json` | Schéma OpenAPI (auto-généré par FastAPI) | + +**Total : 14 pages + 35 API + 1 health = 50 routes applicatives** + +--- + +## Paramètres de requête courants + +Les endpoints paginés (`/api/detections`, `/api/scores`, `/api/traffic`) acceptent : + +| Paramètre | Type | Description | |-----------|------|-------------| -| `page` | int | Page number (default: 1) | -| `page_size` | int | Items per page (default: 20) | -| `threat_level` | string | Filter by threat level | -| `model_name` | string | Filter by model name | -| `search` | string | Full-text search across IP, JA4, host, bot_name | -| `sort_by` | string | Sort field | -| `sort_order` | string | `asc` or `desc` | +| `page` | int | Numéro de page (défaut : 1) | +| `page_size` | int | Éléments par page (défaut : 20) | +| `threat_level` | str | Filtre par niveau de menace | +| `model_name` | str | Filtre par nom de modèle | +| `search` | str | Recherche plein texte (IP, JA4, host, bot_name) | +| `sort_by` | str | Champ de tri (validé contre une whitelist) | +| `sort_order` | str | `asc` ou `desc` | --- -### Investigation (`/api/investigation`) +## Workflow SOC -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/investigation/{ip}/summary` | **Primary investigation endpoint.** Aggregates ML score, brute-force, TCP spoofing, JA4 rotation, persistence, and 24h timeline into a single response with a `risk_score` (0–100) | +Le dashboard est conçu pour le workflow d'un analyste SOC : + +1. **Vue d'ensemble** (`/`) — identifier les tendances de menaces 24h, les pics d'anomalies +2. **Détections** (`/detections`) — filtrer les anomalies par sévérité, rechercher des IPs spécifiques +3. **Investigation IP** (`/ip/{ip}`) — examiner les détections, scores, logs HTTP, features ML, radar de comparaison +4. **Analyse JA4** (`/ja4/{fp}`) — investiguer une empreinte TLS, voir toutes les IPs associées +5. **Campagnes** (`/campaigns`) — visualiser les clusters de bots, graphe réseau, scatter plot +6. **Cluster** (`/cluster/{cid}`) — plonger dans un cluster spécifique, examiner les membres et profils +7. **Tactiques** (`/tactics`) — surveiller le brute-force, la rotation JA4/UA, les menaces récurrentes +8. **Classification** (`/classify`) — soumettre un feedback analyste (bot/légitime/suspect) pour alimenter XGBoost +9. **Features** (`/features`) — comparer les profils de features humain vs bot +10. **Modèles** (`/models`) — surveiller les performances des modèles ML --- -### Reputation (`/api/reputation`) +## Architecture des templates -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/reputation/ip/{ip_address}` | Full IP reputation from IP-API.com and IPinfo.io (proxy, VPN, Tor, hosting detection) | -| GET | `/api/reputation/ip/{ip_address}/summary` | Simplified reputation summary | +### Template de base (`base.html`) + +Fournit le layout commun : + +- Navigation latérale avec liens vers toutes les pages +- Chargement CDN de Tailwind CSS, htmx, Chart.js, ECharts +- Bloc `{% block content %}` pour le contenu de chaque page + +### Rendu côté serveur + htmx + +Chaque page est rendue côté serveur par Jinja2. Les interactions dynamiques utilisent htmx : + +```html + +
+
+``` + +Les graphiques sont rendus côté client par Chart.js et ECharts à partir des données JSON de l'API. --- -### Analysis (`/api/analysis`) +## CORS -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/analysis/{ip}/subnet` | Subnet analysis for an IP (related IPs in same /24) | -| GET | `/api/analysis/{ip}/country` | Country-level analysis for an IP | -| GET | `/api/analysis/country` | Global country analysis across all detections | -| GET | `/api/analysis/{ip}/ja4` | JA4 fingerprint analysis for an IP | -| GET | `/api/analysis/{ip}/user-agents` | User-agent analysis for an IP | -| GET | `/api/analysis/{ip}/recommendation` | SOC classification recommendation | -| POST | `/api/analysis/classifications` | Create a classification (legitimate/suspicious/malicious) | -| GET | `/api/analysis/classifications` | List all classifications | -| GET | `/api/analysis/classifications/stats` | Classification statistics | +Le middleware CORS est configuré de manière permissive dans `main.py` : + +```python +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) +``` + +L'application est principalement SSR mais l'API JSON est aussi consommable par des clients externes. --- -### Entities (`/api/entities`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/entities/types` | List available entity types | -| GET | `/api/entities/subnet/{subnet}` | Investigate a subnet | -| GET | `/api/entities/{entity_type}/{entity_value}` | Investigate any entity (IP, JA4, subnet, UA, host) | -| GET | `/api/entities/{entity_type}/{entity_value}/related` | Related entities | -| GET | `/api/entities/{entity_type}/{entity_value}/user_agents` | User-agents for entity | -| GET | `/api/entities/{entity_type}/{entity_value}/client_headers` | Client headers for entity | -| GET | `/api/entities/{entity_type}/{entity_value}/paths` | URL paths for entity | -| GET | `/api/entities/{entity_type}/{entity_value}/query_params` | Query parameters for entity | - ---- - -### Incidents (`/api/incidents`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/incidents` | List all incidents | -| GET | `/api/incidents/clusters` | Active incident clusters (behavioral similarity grouping) | -| GET | `/api/incidents/{cluster_id}` | Incident cluster details | -| POST | `/api/incidents/{cluster_id}/classify` | Classify an incident cluster | - ---- - -### Fingerprints (`/api/fingerprints`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/fingerprints/spoofing` | TLS fingerprint spoofing detection | -| GET | `/api/fingerprints/ja4-ua-matrix` | JA4 ↔ User-Agent correlation matrix | -| GET | `/api/fingerprints/ua-analysis` | Suspicious user-agent analysis | -| GET | `/api/fingerprints/ip/{ip}/coherence` | Fingerprint coherence analysis per IP | -| GET | `/api/fingerprints/legitimate-ja4` | Known legitimate JA4 fingerprints | -| GET | `/api/fingerprints/asn-correlation` | JA4-ASN correlation analysis | - ---- - -### Brute Force (`/api/bruteforce`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/bruteforce/targets` | Brute-force target hosts | -| GET | `/api/bruteforce/attackers` | Brute-force attacker IPs | -| GET | `/api/bruteforce/timeline` | Brute-force attack timeline | -| GET | `/api/bruteforce/host/{host}/attackers` | Attackers for a specific host | - ---- - -### TCP Spoofing (`/api/tcp-spoofing`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/tcp-spoofing/overview` | TCP/OS fingerprint spoofing overview | -| GET | `/api/tcp-spoofing/list` | Spoofing detection list | -| GET | `/api/tcp-spoofing/matrix` | TTL × MSS anomaly matrix | - ---- - -### Header Fingerprint (`/api/headers`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/headers/clusters` | Header fingerprint clusters (suspicious patterns) | -| GET | `/api/headers/cluster/{hash}/ips` | IPs sharing a header fingerprint | - ---- - -### Heatmap (`/api/heatmap`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/heatmap/hourly` | Hourly traffic heatmap | -| GET | `/api/heatmap/top-hosts` | Top hosts by traffic volume | -| GET | `/api/heatmap/matrix` | Activity/hour matrix | - ---- - -### Botnets (`/api/botnets`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/botnets/ja4-spread` | JA4 geographic spread (botnet indicator) | -| GET | `/api/botnets/ja4/{ja4}/countries` | Country distribution for a JA4 fingerprint | -| GET | `/api/botnets/summary` | Global botnet detection summary | - ---- - -### Rotation (`/api/rotation`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/rotation/ja4-rotators` | IPs rotating JA4 fingerprints (evasion detection) | -| GET | `/api/rotation/persistent-threats` | Persistent threats across time windows | -| GET | `/api/rotation/ip/{ip}/ja4-history` | JA4 fingerprint history for an IP | -| GET | `/api/rotation/sophistication` | Sophistication score analysis | -| GET | `/api/rotation/proactive-hunt` | Proactive threat hunting suggestions | - ---- - -### ML Features (`/api/ml`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/ml/top-anomalies` | Top anomalies with feature details | -| GET | `/api/ml/ip/{ip}/radar` | Feature radar chart data for an IP | -| GET | `/api/ml/score-distribution` | Anomaly score distribution histogram | -| GET | `/api/ml/score-trends` | Score trends over time | -| GET | `/api/ml/b-features` | Source B (TCP/TLS) feature analysis | -| GET | `/api/ml/campaigns` | ML-detected campaign analysis | -| GET | `/api/ml/scatter` | Feature scatter plot data | - ---- - -### Attributes (`/api/attributes`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/attributes/{attr_type}` | List distinct values for an attribute (ja4, user_agent, asn, country, host) with counts | - ---- - -### Variability (`/api/variability`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/variability/{attr_type}/{value}` | Behavioral variability analysis for an attribute value | -| GET | `/api/variability/{attr_type}/{value}/ips` | IPs associated with an attribute value | -| GET | `/api/variability/{attr_type}/{value}/attributes` | Attribute breakdown for a value | -| GET | `/api/variability/{attr_type}/{value}/user_agents` | User-agents for an attribute value | - ---- - -### Clustering (`/api/clustering`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/clustering/status` | Clustering cache status | -| GET | `/api/clustering/clusters` | K-Means cluster list | -| GET | `/api/clustering/cluster/{cluster_id}/points` | Data points in a cluster | -| GET | `/api/clustering/cluster/{cluster_id}/ips` | IPs in a cluster | - ---- - -### Search (`/api/search`) - -| Method | Path | Description | -|--------|------|-------------| -| GET | `/api/search/quick` | Cross-entity search (IP, JA4, host, UA, country, ASN) | - ---- - -### Audit (`/api/audit`) - -| Method | Path | Description | -|--------|------|-------------| -| POST | `/api/audit/logs` | Create an audit log entry | -| GET | `/api/audit/logs` | Query audit logs (filtered, paginated) | -| GET | `/api/audit/stats` | Audit statistics | -| GET | `/api/audit/users/activity` | Per-user activity summary | - -## Frontend Structure - -The React frontend is built with Vite and served as static assets: - -- **Entry point**: `/` → `frontend/dist/index.html` -- **Static assets**: `/assets/*` → `frontend/dist/assets/` -- **SPA routing**: All non-`/api/` paths fall through to `index.html` (React Router) -- **API proxy**: Frontend calls `/api/*` which is handled by FastAPI routers - -## Services - -### IPReputationService - -Queries public IP reputation databases (IP-API.com, IPinfo.io) without API keys: -- Proxy/VPN/Tor detection -- ASN, country, ISP information -- Hosting provider identification - -### ClusteringEngine - -K-Means clustering on ML features with caching: -- Automatic cluster count selection -- Feature normalization via StandardScaler -- In-memory cache with TTL - -## Deployment +## Déploiement ```bash -# Build Docker image +# Construction de l'image Docker make build-dashboard -# Run tests +# Tests make test-dashboard -# Run locally (development) +# Exécution locale (développement) cd services/dashboard uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000 ``` -### Health Check +### Point de santé ``` GET /health → {"status": "healthy", "clickhouse": "connected"} diff --git a/docs/shared/python-ja4common.md b/docs/shared/python-ja4common.md index 5d51416..a0d3d63 100644 --- a/docs/shared/python-ja4common.md +++ b/docs/shared/python-ja4common.md @@ -1,64 +1,106 @@ # python-ja4common -`ja4_common` is the shared Python library for the ja4-platform, providing a unified ClickHouse client singleton and configuration settings. It is used by [bot-detector](../services/bot-detector.md) and [dashboard](../services/dashboard.md). +Bibliothèque Python partagée pour la plateforme ja4, fournissant un client +ClickHouse singleton et une configuration centralisée via pydantic-settings. -**Package name**: `ja4-common` +**Utilisée par** : [bot-detector](../services/bot-detector.md) (via `ja4_common.clickhouse`) -**Python version**: ≥ 3.11 +> **Note :** le [dashboard](../services/dashboard.md) n'utilise **pas** `ja4_common`. Il possède +> son propre client `clickhouse-connect` léger dans `backend/database.py`. -**Dependencies**: -- `clickhouse-connect >= 0.8.0` -- `pydantic-settings >= 2.1.0` +--- + +## Informations du package + +| Clé | Valeur | +|-----|--------| +| **Nom du package** | `ja4-common` | +| **Version** | `0.1.0` | +| **Python** | ≥ 3.11 | +| **Dépendances** | `clickhouse-connect >= 0.8.0`, `pydantic-settings >= 2.1.0` | +| **Build system** | `setuptools >= 68` + `wheel` | + +--- ## ClickHouseSettings -Pydantic-settings model that reads configuration from environment variables and `.env` files. +Modèle pydantic-settings qui lit la configuration depuis les variables d'environnement +et les fichiers `.env`. -### Fields +### Champs -| Field | Type | Default | Env Variable | Description | -|-------|------|---------|-------------|-------------| -| `CLICKHOUSE_HOST` | str | `"clickhouse"` | `CLICKHOUSE_HOST` | ClickHouse server hostname | -| `CLICKHOUSE_PORT` | int | `8123` | `CLICKHOUSE_PORT` | ClickHouse HTTP API port | -| `CLICKHOUSE_DB` | str | `"ja4_processing"` | `CLICKHOUSE_DB` | Database name | -| `CLICKHOUSE_USER` | str | `"admin"` | `CLICKHOUSE_USER` | Username for authentication | -| `CLICKHOUSE_PASSWORD` | str | `""` | `CLICKHOUSE_PASSWORD` | Password for authentication | +| Champ | Type | Défaut | Variable d'env | Description | +|-------|------|--------|----------------|-------------| +| `CLICKHOUSE_HOST` | str | `"clickhouse"` | `CLICKHOUSE_HOST` | Nom d'hôte du serveur ClickHouse | +| `CLICKHOUSE_PORT` | int | `8123` | `CLICKHOUSE_PORT` | Port de l'API HTTP ClickHouse | +| `CLICKHOUSE_DB` | str | `"ja4_processing"` | `CLICKHOUSE_DB` | Base de données par défaut (rétro-compatibilité) | +| `CLICKHOUSE_DB_PROCESSING` | str | `"ja4_processing"` | `CLICKHOUSE_DB_PROCESSING` | Base de données ML, agrégations, dictionnaires | +| `CLICKHOUSE_DB_LOGS` | str | `"ja4_logs"` | `CLICKHOUSE_DB_LOGS` | Base de données des logs HTTP bruts | +| `CLICKHOUSE_USER` | str | `"admin"` | `CLICKHOUSE_USER` | Utilisateur pour l'authentification | +| `CLICKHOUSE_PASSWORD` | str | `""` | `CLICKHOUSE_PASSWORD` | Mot de passe pour l'authentification | -### Configuration Sources +### Schéma dual-database -Settings are loaded in order of precedence: -1. **Environment variables** (highest priority) -2. **`.env` file** in the current working directory -3. **Default values** (lowest priority) +La plateforme utilise deux bases de données ClickHouse : -Environment variable names are **case-sensitive** (e.g., `CLICKHOUSE_HOST`, not `clickhouse_host`). +- **`CLICKHOUSE_DB_PROCESSING`** (`ja4_processing`) : tables ML (`ml_detected_anomalies`, + `ml_all_scores`), agrégations (`agg_*`), dictionnaires (`dict_*`), feedback SOC, audit +- **`CLICKHOUSE_DB_LOGS`** (`ja4_logs`) : `http_logs_raw`, `http_logs`, vues matérialisées -### Usage +Des **références croisées** existent entre les deux bases — les vues matérialisées de l'une +lisent dans l'autre. Utiliser toujours des noms de tables pleinement qualifiés : ```python from ja4_common.settings import settings -print(settings.CLICKHOUSE_HOST) # "clickhouse" or from env -print(settings.CLICKHOUSE_PORT) # 8123 or from env +query = f"SELECT ... FROM {settings.CLICKHOUSE_DB_PROCESSING}.ml_detected_anomalies ..." +query = f"SELECT ... FROM {settings.CLICKHOUSE_DB_LOGS}.http_logs ..." ``` +Ne jamais coder en dur les noms de bases dans les requêtes. + +### Sources de configuration + +Les paramètres sont chargés par ordre de priorité : +1. **Variables d'environnement** (priorité la plus haute) +2. **Fichier `.env`** dans le répertoire courant +3. **Valeurs par défaut** (priorité la plus basse) + +Les noms de variables d'environnement sont **sensibles à la casse** (ex. `CLICKHOUSE_HOST`, pas `clickhouse_host`). + +### Utilisation + +```python +from ja4_common.settings import settings + +print(settings.CLICKHOUSE_HOST) # "clickhouse" ou depuis l'env +print(settings.CLICKHOUSE_PORT) # 8123 ou depuis l'env +print(settings.CLICKHOUSE_DB_PROCESSING) # "ja4_processing" ou depuis l'env +print(settings.CLICKHOUSE_DB_LOGS) # "ja4_logs" ou depuis l'env +``` + +Le singleton `settings` est créé au niveau du module à l'import. + +--- + ## ClickHouseClient -Wraps `clickhouse_connect` with auto-reconnection and a clean API. +Encapsule `clickhouse_connect` avec reconnexion automatique et une API épurée. -### Methods +### Méthodes -| Method | Signature | Description | -|--------|-----------|-------------| -| `connect` | `connect() -> Client` | Returns the underlying `clickhouse_connect` client, creating or reconnecting as needed | -| `query` | `query(query: str, params: dict = None)` | Execute a SELECT query, returns result set | -| `command` | `command(query: str, params: dict = None)` | Execute a DDL/DML command (CREATE, INSERT, etc.) | -| `insert` | `insert(table: str, data, column_names=None)` | Bulk insert data into a table | -| `close` | `close()` | Close the connection and release resources | +| Méthode | Signature | Description | +|---------|-----------|-------------| +| `connect` | `connect() → Client` | Retourne le client `clickhouse_connect` sous-jacent, crée ou reconnecte si nécessaire | +| `_ping` | `_ping() → bool` | Vérifie la connexion via `client.ping()`, retourne `False` en cas d'exception | +| `query` | `query(query: str, params: dict = None)` | Exécute une requête SELECT, retourne le résultat | +| `command` | `command(query: str, params: dict = None)` | Exécute une commande DDL/DML (CREATE, INSERT, etc.) | +| `insert` | `insert(table: str, data, column_names=None)` | Insertion en masse dans une table | +| `close` | `close()` | Ferme la connexion et libère les ressources | -### Auto-Reconnection +### Reconnexion automatique -The `connect()` method automatically reconnects if the current connection is lost: +La méthode `connect()` reconnecte automatiquement si la connexion est perdue : ```python def connect(self): @@ -74,41 +116,50 @@ def connect(self): return self._client ``` -### Usage Example +### Exemple d'utilisation ```python from ja4_common.clickhouse import get_client client = get_client() -# SELECT query -result = client.query("SELECT count() FROM http_logs WHERE src_ip = {ip:String}", {"ip": "203.0.113.42"}) +# Requête SELECT avec noms de tables pleinement qualifiés +result = client.query( + f"SELECT count() FROM {settings.CLICKHOUSE_DB_LOGS}.http_logs " + "WHERE src_ip = {ip:String}", + {"ip": "203.0.113.42"}, +) print(result.result_rows) # INSERT -client.insert("audit_logs", [[datetime.now(), "analyst1", "investigate", "ip", "203.0.113.42"]], - column_names=["timestamp", "user_name", "action", "entity_type", "entity_id"]) +client.insert( + "audit_logs", + [[datetime.now(), "analyst1", "investigate", "ip", "203.0.113.42"]], + column_names=["timestamp", "user_name", "action", "entity_type", "entity_id"], +) -# Command +# Commande DDL client.command("OPTIMIZE TABLE http_logs FINAL") ``` -## get_client() Singleton +--- -The `get_client()` function provides a module-level singleton `ClickHouseClient`: +## Singleton `get_client()` + +La fonction `get_client()` fournit un singleton de `ClickHouseClient` au niveau du module : ```python from ja4_common.clickhouse import get_client -# First call creates the client +# Le premier appel crée le client client1 = get_client() -# Subsequent calls return the same instance +# Les appels suivants retournent la même instance client2 = get_client() assert client1 is client2 ``` -### Implementation +### Implémentation ```python _client: Optional[ClickHouseClient] = None @@ -120,16 +171,40 @@ def get_client() -> ClickHouseClient: return _client ``` -## Using from a New Service +Architecture à **deux niveaux de singleton** : +- `get_client()` → singleton pour `ClickHouseClient` +- `settings` dans `settings.py` → singleton pour `ClickHouseSettings` -### 1. Add Dependency +--- -In your service's `requirements.txt`: +## Exports du package + +Le `__init__.py` n'exporte qu'une chaîne de version : + +```python +"""JA4 Common — shared utilities for the JA4 security suite.""" +__version__ = "0.1.0" +``` + +Les consommateurs doivent importer directement depuis les sous-modules : + +```python +from ja4_common.clickhouse import get_client +from ja4_common.settings import settings +``` + +--- + +## Intégration dans un nouveau service + +### 1. Ajouter la dépendance + +Dans le `requirements.txt` du service : ``` ja4-common @ file:///app/shared/python/ja4_common ``` -Or in `pyproject.toml`: +Ou dans `pyproject.toml` : ```toml [project] dependencies = [ @@ -137,54 +212,62 @@ dependencies = [ ] ``` -### 2. Docker Setup +### 2. Configuration Docker ```dockerfile -# Copy shared library +# Copier la bibliothèque partagée COPY shared/python/ja4_common /app/shared/python/ja4_common RUN pip install /app/shared/python/ja4_common -# Copy service code -COPY services/my-service /app/services/my-service +# Copier le code du service +COPY services/mon-service /app/services/mon-service ``` -### 3. Use in Code +### 3. Utiliser dans le code ```python from ja4_common.clickhouse import get_client from ja4_common.settings import settings -# Access settings -print(f"Connecting to {settings.CLICKHOUSE_HOST}:{settings.CLICKHOUSE_PORT}") +# Accéder à la configuration +print(f"Connexion à {settings.CLICKHOUSE_HOST}:{settings.CLICKHOUSE_PORT}") +print(f"Base logs : {settings.CLICKHOUSE_DB_LOGS}") +print(f"Base processing : {settings.CLICKHOUSE_DB_PROCESSING}") -# Use client +# Utiliser le client avec noms pleinement qualifiés db = get_client() -result = db.query("SELECT count() FROM ml_detected_anomalies") +result = db.query( + f"SELECT count() FROM {settings.CLICKHOUSE_DB_PROCESSING}.ml_detected_anomalies" +) ``` -### 4. Environment Configuration +### 4. Configuration d'environnement -Create a `.env` file or set environment variables: +Créer un fichier `.env` ou définir les variables d'environnement : ```bash CLICKHOUSE_HOST=clickhouse.example.com CLICKHOUSE_PORT=8123 CLICKHOUSE_DB=ja4_processing +CLICKHOUSE_DB_PROCESSING=ja4_processing +CLICKHOUSE_DB_LOGS=ja4_logs CLICKHOUSE_USER=data_writer CLICKHOUSE_PASSWORD=secret ``` -## Testing: Mocking the Client +--- -### Using unittest.mock +## Tests : simuler le client + +### Avec unittest.mock ```python from unittest.mock import MagicMock, patch from ja4_common.clickhouse import ClickHouseClient -def test_my_service(): +def test_mon_service(): mock_client = MagicMock(spec=ClickHouseClient) mock_client.query.return_value = MagicMock(result_rows=[(42,)]) - + with patch("ja4_common.clickhouse._client", mock_client): from ja4_common.clickhouse import get_client client = get_client() @@ -192,25 +275,32 @@ def test_my_service(): assert result.result_rows == [(42,)] ``` -### Overriding Settings in Tests +### Surcharger les paramètres en test ```python from ja4_common.settings import ClickHouseSettings -# Create custom settings for tests +# Créer des paramètres personnalisés pour les tests test_settings = ClickHouseSettings( CLICKHOUSE_HOST="localhost", CLICKHOUSE_PORT=8123, CLICKHOUSE_DB="test_db", + CLICKHOUSE_DB_PROCESSING="test_processing", + CLICKHOUSE_DB_LOGS="test_logs", CLICKHOUSE_USER="test_user", CLICKHOUSE_PASSWORD="test_pass", ) ``` -## Source Files +--- -| File | Description | -|------|-------------| -| `ja4_common/settings.py` | `ClickHouseSettings` pydantic-settings model | -| `ja4_common/clickhouse.py` | `ClickHouseClient` class and `get_client()` singleton | -| `pyproject.toml` | Package metadata and dependencies | +## Fichiers sources + +| Fichier | Description | +|---------|-------------| +| `ja4_common/__init__.py` | Docstring du package et `__version__` | +| `ja4_common/settings.py` | Modèle pydantic-settings `ClickHouseSettings` et singleton `settings` | +| `ja4_common/clickhouse.py` | Classe `ClickHouseClient` et singleton `get_client()` | +| `pyproject.toml` | Métadonnées du package et dépendances | +| `tests/test_settings.py` | Tests unitaires pour `ClickHouseSettings` | +| `tests/test_clickhouse.py` | Tests unitaires pour `ClickHouseClient` |