feat: ja4-platform monorepo — 5 services unified, tests & RPM builds standardized

Services:
- ja4sentinel: TLS/JA4 fingerprint capture daemon (Go, libpcap)
- logcorrelator: JA4 log correlation engine (Go, ClickHouse)
- mod_reqin_log: Apache module (C, JSON request logging)
- bot_detector: ML bot detection pipeline (Python)
- dashboard: FastAPI/Streamlit analytics UI (Python)

Shared libraries:
- shared/go/ja4common: logger, config, shutdown, ipfilter (Go module)
- shared/python/ja4_common: ClickHouseClient, ClickHouseSettings (Python package)
- shared/clickhouse/: canonical SQL migrations (10 files)

Build & packaging:
- Unified 3-stage Dockerfile.package for Go RPMs (el8/el9/el10)
- go.work workspace linking sentinel, correlator, ja4common
- Makefile with test-all, build-all, rpm-* targets

Fixes applied:
- go.work: 1.21 → 1.24.6 (required by sentinel)
- correlator Dockerfiles: golang:1.21 → golang:1.24
- replace directives in go.mod for ja4common local path
- pyproject.toml: setuptools.backends → setuptools.build_meta
- Removed static libpcap linking (unavailable on Rocky 9)
- Fixed data races in output/writers_test.go (sync.Mutex + atomic.Int32)
- Rewrote corrupted test files (logger_test.go × 2)

Test coverage:
- correlator: 67.1% total (unixsocket 80.5%, config 91.7%, app 83.3%, multi 87.7%, stdout 100%)
- sentinel: all 10 packages pass (api, capture, config, fingerprint, ipfilter, logging, output, tlsparse)

Documentation:
- README.md + docs/ (architecture, development, 5 services, shared libs, DB schema & migrations)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-07 16:42:59 +02:00
commit d469e39da7
278 changed files with 1621301 additions and 0 deletions

View File

@ -0,0 +1,242 @@
# Rapport Final — SOC Bot Detector Dashboard
**Date :** 2026-03-16
**Commits :** `8032eba` (corrections bugs), `d4c3512` (améliorations)
---
## 1. Corrections de bugs (commit 8032eba)
| Bug | Cause | Correction |
|-----|-------|-----------|
| Brute Force > Attaquants : IPs affichées en `::ffff:x.x.x.x` | Pas de normalisation IPv6 dans la requête SQL | `replaceRegexpAll(toString(src_ip), '^::ffff:', '')` ajouté |
| Brute Force > Cibles : lien "Voir détails" → page inexistante | Navigation vers `/investigation/{host}` (hostname) au lieu d'une IP | Remplacement par composant `TargetRow` avec expansion inline des attaquants par host |
| Header Fingerprint : tableau de détail toujours vide | Frontend lisait `data.ips` au lieu de `data.items` | Correction de la clé |
| Heatmap Temporelle : "Top hosts ciblés" vide | Frontend lisait `data.hosts` + erreur de type TypeScript `{ hosts: TopHost[] }` | Correction clé `data.items` + type annotation |
| Botnets Distribués : clic sur ligne n'affiche rien | Frontend lisait `data.countries` au lieu de `data.items` | Correction de la clé |
| Rotation & Persistance : IPs en `::ffff:` + historique toujours vide | Pas de normalisation + frontend lisait `data.history` au lieu de `data.ja4_history` | Normalisation SQL + correction de la clé |
| TCP Spoofing : spoofings détectés sans corrélation TTL | Filtre Python-side sur données déjà filtrées TTL=3031 | Filtre SQL `spoof_only` déplacé côté ClickHouse |
---
## 2. Améliorations implémentées (commit d4c3512)
### J — Synthèse IP multi-sources
- **Endpoint :** `GET /api/investigation/{ip}/summary`
- **Widget :** `IPActivitySummary` en haut de toute page d'investigation IP
- **Données :** ML + bruteforce + TCP spoofing + JA4 rotation + persistance + timeline 24h
- **Score de risque :** 0100 (jauge SVG colorée)
- **Résultat :** Contexte immédiat en un coup d'œil, sans naviguer entre 6 pages
### I — Comparaison baseline 24h/hier
- **Endpoint :** `GET /api/metrics/baseline`
- **Widget :** 3 cartes (Détections 24h, IPs uniques, CRITICAL) avec variation ▲▼ en %
- **Impact :** Détecte immédiatement les pics anormaux (ex: +246% détections observé)
### M-4 — Score de sophistication adversaire
- **Endpoint :** `GET /api/rotation/sophistication`
- **Calcul :** JOIN 3 tables (rotation JA4 × 10 + récurrence × 20 + log(bruteforce+1) × 5)
- **Tiers :** APT-like / Advanced / Automated / Basic
- **Résultat :** Prioritisation des enquêtes les plus urgentes
### M-7 — Chasse proactive (low-and-slow)
- **Endpoint :** `GET /api/rotation/proactive-hunt`
- **Logique :** IPs récurrentes avec `abs(anomaly_score) < 0.5` — volent sous le radar ML
- **Évaluation :** "Évadeur potentiel" (ratio récurrence/score > 10) ou "Persistant modéré"
- **Impact :** Détecte les botnets slow-and-low que le modèle ML sous-score
### M-2 — Badge réputation ASN inline
- **Modification :** LEFT JOIN `asn_reputation` dans la requête des détections
- **Badge :** Rouge (malicious/bot/scanner), orange (proxy/vpn), vert (human)
- **Limitation :** La table `asn_reputation` contient 36 ASN français (ISPs légitimes) — les ASNs malveillants connus ne sont pas encore catalogués
---
## 3. Tests exhaustifs Playwright
| Page | Résultat | Notes |
|------|----------|-------|
| Dashboard principal | ✅ | Baseline ▲ +246.5% détections, ▲ +11.6% IPs, = CRITICAL |
| Détections | ✅ | Badge ASN affiché (null pour ASNs hors table reputation) |
| Investigation IP (162.55.94.175) | ✅ | Score 38, TCP Spoof TTL 59, JA4 Rotation 9 sig |
| Rotation > Sophistication | ✅ | APT-like: 162.55.94.175 (score 100), 46.4.81.149 (score 100) |
| Rotation > Chasse proactive | ✅ | IPs avec scores négatifs sous le radar ML |
| Brute Force > Attaquants | ✅ | IPs propres (sans `::ffff:`) |
| Brute Force > Cibles | ✅ | Expansion inline des attaquants par host |
| Header Fingerprint | ✅ | Tableau détail rempli au clic |
| Heatmap Temporelle | ✅ | Top hosts ciblés affiché |
| Botnets Distribués | ✅ | Détail pays au clic |
| TCP Spoofing | ✅ | Filtre `spoof_only` fonctionnel |
---
## 4. Points problématiques et axes d'amélioration
### 🔴 Critiques
1. **Table `asn_reputation` incomplète** — 36 entrées uniquement (ISPs français). Pour être utile, elle devrait contenir les ASNs des datacenters, VPS, proxies connus (OVH, DigitalOcean, AWS, Linode, etc.). Source suggérée : AbuseIPDB ASN database, IPInfo, Maxmind.
2. **Chasse proactive — scores négatifs**`view_ip_recurrence.worst_score` stocke le score brut (peut être négatif). La condition `abs(score) < 0.5` capture des IPs HIGH avec score -0.18 qui sont déjà détectées par ML. Il faudrait filtrer par niveau de menace (`worst_threat_level NOT IN ('HIGH', 'CRITICAL')`) pour vraiment identifier les cas sous le radar.
3. **Pas de persistance des classifications SOC** — Les classifications manuelles (`/api/analysis/classify`) ne persistent que pendant la session si la table `classifications` n'est pas créée. Un script d'init DB serait utile.
### 🟡 Moyens
4. **Score de sophistication biaised** — Les IPs avec forte rotation JA4 mais `recurrence=0` dans `view_ip_recurrence` (non présentes) atteignent quand même score 100. Les données des deux vues ne sont pas toujours cohérentes sur la même période temporelle.
5. **Timeline 24h dans la synthèse IP** — Utilise `window_start >= now() - INTERVAL 24 HOUR` sur `agg_host_ip_ja4_1h`. Si les données ont moins de 24h d'historique, le graphique sera partiel/vide. Adapter la fenêtre dynamiquement selon les données disponibles.
6. **Heatmap Temporelle** — Les données de `agg_host_ip_ja4_1h` ne sont agrégées que pour les dernières 24h dans l'endpoint. Un sélecteur de plage temporelle (7j, 30j) permettrait de détecter les patterns de vagues cycliques (botnets hebdomadaires).
7. **Pas d'export des résultats** — Les analystes SOC ne peuvent pas exporter les listes d'IPs malveillantes (CSV, STIX). Un endpoint `GET /api/rotation/sophistication?format=csv` serait utile pour l'IOC sharing.
### 🟢 Mineurs
8. **"Investiguer" dans le RotationView ne transmet pas le contexte** — Un clic sur "Investiguer" depuis l'onglet Sophistication navigue vers `/investigation/{ip}` sans pré-charger le contexte de l'onglet source. Un `?source=sophistication&score=100` dans l'URL permettrait d'afficher un bandeau contextuel.
9. **Onglets non présents dans la sidebar** — Les 7 dashboards d'analyse avancée ne sont pas organisés en sous-menus. Avec l'ajout des onglets Sophistication et Chasse proactive dans Rotation, la sidebar commence à être longue.
10. **Badge ASN ne trie pas les détections** — Il n'y a pas encore de filtre "Afficher seulement les ASNs malveillants" dans les détections.
---
## 5. Architecture — points de vigilance
- Le **SPA catch-all** (`/{full_path:path}`) doit rester **le dernier router** dans `main.py`
- L'endpoint `/api/investigation/{ip}/summary` utilise le préfixe `/api/investigation` — compatible avec la route SPA `/investigation/:ip` (distinct)
- Les **scores négatifs** dans `anomaly_score` et `worst_score` sont normaux — toujours utiliser `abs()` pour l'affichage
- Les **IPv6-mapped** (`::ffff:x.x.x.x`) sont présentes dans toutes les vues agrégées — systématiquement utiliser `replaceRegexpAll(toString(src_ip), '^::ffff:', '')`
---
# Rapport — v2.0.0 : TCP Fingerprinting Multi-Signal + Clustering IPs
**Date :** 2026-03-19
**Commit :** `e2db8ca`
---
## 1. TCP Fingerprinting OS amélioré
### Problème initial
L'ancien `tcp_spoofing.py` utilisait uniquement le TTL avec 3 plages grossières (≤64 = Linux, ≤128 = Windows, sinon = Network). Résultat : faux positifs, aucune détection de bots scanners.
### Solution implémentée
**`backend/services/tcp_fingerprint.py`** — 20 signatures OS, scoring multi-signal :
| Signal | Poids | Source ClickHouse |
|--------|-------|------------------|
| TTL initial (estimé) | 40% | `tcp_ttl_raw` |
| MSS | 30% | `tcp_mss_raw` |
| Fenêtre TCP | 20% | `tcp_win_raw` |
| Scale factor | 10% | `tcp_scale_raw` |
**Détections validées en production :**
- **Masscan** : `win=5808, mss=1452, scale=4, TTL 4857` → confiance **97%**
- **Googlebot** : stack Windows détecté avec UA Android → **spoof confirmé**
- **Bot-tool** : `risk_score += 30` (vs +15 pour spoof simple)
**MSS → chemin réseau :**
- 1460 → Ethernet standard
- 1452 → PPPoE / DSL (Masscan pattern)
- 14201452 → VPN probable
- < 1420 Tunnel / double-encapsulation
**Fichiers modifiés :**
- `backend/services/tcp_fingerprint.py` (nouveau)
- `backend/routes/tcp_spoofing.py` (réécriture complète queries `agg_host_ip_ja4_1h`)
- `backend/routes/investigation_summary.py` (utilise le service tcp_fingerprint)
- `frontend/src/components/TcpSpoofingView.tsx` (nouvelles colonnes MSS/scale/confiance, graphique distribution MSS)
---
## 2. Clustering IPs multi-métriques
### Problème initial
La première version du clustering utilisait uniquement des règles sur les propriétés TCP. L'utilisateur a demandé d'utiliser **l'ensemble des métriques disponibles**.
### Solution implémentée
**`backend/services/clustering_engine.py`** K-means++ pur Python (sans dépendances ML) :
**21 features normalisées [0,1] :**
| Catégorie | Features |
|-----------|----------|
| Stack TCP (4) | TTL initial, MSS, scale, fenêtre |
| Anomalie ML (6) | score, vélocité, fuzzing, headless, POST ratio, IP-ID zéro |
| TLS/Protocole (5) | ALPN mismatch, ALPN absent, efficacité H2, ordre headers, UA-CH mismatch |
| Navigateur (1) | score navigateur moderne (normalisé /50) |
| Temporel (3) | entropie, diversité JA4 (log1p), UA rotatif |
| Comportement (2) | ratio assets, ratio accès direct |
**Algorithme :**
```
K-means++ : init O(k·n), n_init=3, meilleure inertie retenue
Power iter : X^T(Xv) trick, O(n·d) par iter — pas de matrice n×n
Déflation : Hotelling pour PC2 après extraction PC1
```
**Stratégie d'échantillonnage :** `ORDER BY avg(abs(anomaly_score)) DESC` les bots (score élevé) sont inclus en priorité, même si leurs hits individuels sont faibles (cas Masscan).
**Résultats en production (k=14, 3000 IPs) :**
- **289 bots confirmés** : clusters UA rotatif + UA-CH mismatch (cloud providers : Microsoft, Google, Akamai)
- **655 IPs suspects** : anomalie ML modérée ou UA-CH incohérent
- **ASN dominants** : MICROSOFT-CORP-MSN-AS-BLOCK, GOOGLE-CLOUD-PLATFORM, OVH, AMAZON
- **Temps de calcul** : ~59 secondes (Python pur, 3000 points × 21 features)
---
## 3. Visualisation clustering redesignée
### Problème initial
La première version utilisait des bulles ReactFlow positionnées par PCA. L'utilisateur a signalé : **"l'affichage du graphe est illisible"**.
### Solution implémentée
**Deux vues distinctes, accessibles par onglets :**
#### ⊞ Tableau de bord (défaut — toujours lisible)
- Grille de cartes groupées par niveau de risque
- **Bots & Menaces confirmées** (rouge) **Suspects** (orange) **Légitimes** (vert)
- Chaque carte : label + IP count + hits + badge CRITIQUE/ÉLEVÉ/MODÉRÉ/SAIN + 4 mini-barres + stack TCP + pays + ASN
#### ⬡ Graphe de relations
- Nœuds-cartes ReactFlow (220px texte entièrement lisible)
- **Colonnes par niveau de menace** (disposition déterministe, pas PCA)
- Arêtes colorées : orange=similaire, gris=distant, animé=très fort
- Légende intégrée, minimap, contrôles zoom
#### Sidebar de détail
- RadarChart comportemental (10 axes)
- Toutes les métriques avec barres de progression
- Liste des IPs avec badges menace/pays
- Export **Copier IPs** + ** CSV**
- Intégrée dans le flux flex (ne bloque plus la barre de contrôle)
**Fichiers modifiés :**
- `backend/routes/clustering.py` (réécriture complète)
- `backend/services/clustering_engine.py` (nouveau seuils calibrés sur données réelles)
- `frontend/src/components/ClusteringView.tsx` (réécriture complète)
- `frontend/src/App.tsx` (route `/clustering` + nav "🔬 Clustering IPs")
---
## 4. Points d'attention
### Performances
- K-means++ sur 3000 × 21 : **59s** (acceptable pas de cache implémenté)
- Le cache mémoire du drill-down (`_cache["cluster_ips"]`) est volatile : rechargement = recalcul
- Pour améliorer : cache Redis ou TTL 5 min avec `functools.lru_cache`
### Calibration des seuils
Les seuils de `name_cluster()` et `risk_score_from_centroid()` sont calibrés sur les données observées :
- `anomaly_score` en production : plage 0.20.35 (pas 01 comme attendu)
- Score normalisé affiché : `min(1, score / 0.5)` pour étirer la plage utile
- UA-CH mismatch = 1.0 sur les clusters bot = signal **très fort** (cloud providers simulant un navigateur)
### Données manquantes dans le LEFT JOIN
Certaines IPs n'apparaissent pas dans `ml_detected_anomalies` (score=0, fuzz=0). Ce sont les IPs légitimes non détectées par le modèle ML. Elles forment naturellement les clusters "Trafic Légitime".
### Fuzzing_index = 100% dans beaucoup de clusters
Après analyse : le `fuzzing_index` log-normalisé dépasse souvent le seuil de 100% car les valeurs brutes sont très variables (0 à 229+). Ce n'est pas un bug c'est la nature du trafic web moderne (beaucoup de requêtes avec des paths variés).