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:
204
services/bot-detector/CLICKHOUSE_FEATURES_DIAGNOSTIC.md
Normal file
204
services/bot-detector/CLICKHOUSE_FEATURES_DIAGNOSTIC.md
Normal file
@ -0,0 +1,204 @@
|
||||
# Diagnostic — Features manquantes dans `view_ai_features_1h`
|
||||
|
||||
> Généré le 2026-03-17 — Mis à jour le 2026-03-17 (corrections appliquées) — À destination de l'administrateur ClickHouse
|
||||
|
||||
## ✅ Statut des corrections (2026-03-17 13:05)
|
||||
|
||||
| Problème | Correction appliquée | Résultat |
|
||||
|----------|---------------------|----------|
|
||||
| **1** — MV `mv_agg_header_fingerprint_1h` absente | MV recréée + backfill 25h | ✅ 10 features header actives |
|
||||
| **2** — `header_order_shared_count` / `distinct_header_orders` globales | Se corrige avec Problème 1 | ✅ Résolu automatiquement |
|
||||
| **3** — `orphan_ratio` = 0 pour `correlated=1` | Comportement normal (by design) | ℹ️ Pas d'action requise |
|
||||
| **4** — 4 vues dashboard absentes | Vues créées | ✅ |
|
||||
| **5** — `view_dashboard_variability` référence `header_user_agent` inexistant | Colonne remplacée par `reason` | ✅ Bug corrigé |
|
||||
| **6** — Anciennes vues heuristiques orphelines | Droppées | ✅ |
|
||||
|
||||
Cycle post-correction (13:05) — features dans les warnings :
|
||||
- `Complet` : seulement `orphan_ratio` (by design)
|
||||
- `Applicatif` : `request_size_variance`, `mss_mobile_mismatch`, `is_rare_ja4` (see §4 below)
|
||||
- Header features **disparues des warnings** → pipeline opérationnel ✅
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Résumé
|
||||
|
||||
Le service Bot Detector signale des **features non-discriminantes** à chaque cycle. Ce document en explique les causes exactes et les corrections nécessaires côté ClickHouse.
|
||||
|
||||
Ces avertissements **n'empêchent pas le service de fonctionner** — les features invalides sont automatiquement exclues du modèle (A7). Mais leur absence réduit la qualité de la détection.
|
||||
|
||||
---
|
||||
|
||||
## Problème 1 — Pipeline `agg_header_fingerprint_1h` arrêté ⚠️ CRITIQUE
|
||||
|
||||
### Symptôme
|
||||
|
||||
Les features suivantes sont toujours à **0** dans `view_ai_features_1h` :
|
||||
|
||||
- `header_count`
|
||||
- `has_accept_language`
|
||||
- `has_cookie`
|
||||
- `has_referer`
|
||||
- `modern_browser_score`
|
||||
- `ua_ch_mismatch`
|
||||
- `mss_mobile_mismatch` *(dépend de `modern_browser_score`)*
|
||||
|
||||
### Cause
|
||||
|
||||
La table `mabase_prod.agg_header_fingerprint_1h` (AggregatingMergeTree) n'a plus reçu de données depuis le **2026-03-13 23:00** :
|
||||
|
||||
```sql
|
||||
SELECT max(window_start), count()
|
||||
FROM mabase_prod.agg_header_fingerprint_1h;
|
||||
-- Résultat : 2026-03-13 23:00:00, 73024 lignes
|
||||
```
|
||||
|
||||
La vue fait un `LEFT JOIN` avec condition `window_start >= now() - INTERVAL 24 HOUR`, et comme aucune ligne récente n'existe dans `agg_header_fingerprint_1h`, **toutes les colonnes issues de ce JOIN retournent NULL** (→ 0 après coalesce).
|
||||
|
||||
### Recherche de la MV source
|
||||
|
||||
La liste des Materialized Views ne montre aucune MV dédiée à `agg_header_fingerprint_1h` :
|
||||
|
||||
```sql
|
||||
SELECT name FROM system.tables
|
||||
WHERE database = 'mabase_prod' AND engine = 'MaterializedView';
|
||||
-- mv_agg_host_ip_ja4_1h
|
||||
-- mv_http_logs
|
||||
-- view_dashboard_entities_mv
|
||||
-- view_dashboard_user_agents_mv
|
||||
```
|
||||
|
||||
Aucune MV ne cible `agg_header_fingerprint_1h`. Elle est probablement alimentée par un **processus externe** (ETL, script, pipeline Kafka, etc.) qui s'est arrêté.
|
||||
|
||||
### Correction appliquée ✅
|
||||
|
||||
La MV `mv_agg_header_fingerprint_1h` était **définie dans `deploy_views.sql`** mais n'avait jamais été créée en base. Elle a été recréée le 2026-03-17 :
|
||||
|
||||
```sql
|
||||
-- Recréation de la MV (déjà appliquée)
|
||||
CREATE MATERIALIZED VIEW mabase_prod.mv_agg_header_fingerprint_1h
|
||||
TO mabase_prod.agg_header_fingerprint_1h AS
|
||||
SELECT
|
||||
toStartOfHour(src.time) AS window_start,
|
||||
toIPv6(src.src_ip) AS src_ip,
|
||||
any(toString(cityHash64(src.client_headers))) AS header_order_hash,
|
||||
max(toUInt16(length(src.client_headers) - length(replaceAll(src.client_headers, ',', '')) + 1)) AS header_count,
|
||||
-- ... (voir deploy_views.sql §5)
|
||||
FROM mabase_prod.http_logs AS src
|
||||
GROUP BY window_start, src.src_ip;
|
||||
```
|
||||
|
||||
Un **backfill de 25 heures** a été effectué depuis `http_logs` pour alimenter la table avec des données historiques (377 689 lignes insérées). Les nouvelles données sont désormais alimentées en temps réel par la MV.
|
||||
|
||||
### Cause historique
|
||||
|
||||
La MV avait été omise lors du déploiement initial. La table `agg_header_fingerprint_1h` contenait 73 024 lignes datant du 2026-03-13 (probablement issues d'un backfill manuel ponctuel), puis n'avait plus été alimentée.
|
||||
|
||||
---
|
||||
|
||||
## Problème 2 — Features non-discriminantes (agrégat global, non per-IP)
|
||||
|
||||
### Symptôme
|
||||
|
||||
Les features suivantes ont une **valeur unique non-nulle identique pour toutes les IPs** :
|
||||
|
||||
- `header_order_shared_count` (valeur ≈ 421 000 pour toutes les lignes)
|
||||
- `distinct_header_orders` (valeur identique pour toutes les lignes)
|
||||
|
||||
### Cause
|
||||
|
||||
Ces features sont calculées via des window functions `PARTITION BY header_order_hash` :
|
||||
|
||||
```sql
|
||||
-- Dans la vue :
|
||||
count() OVER (PARTITION BY h.header_order_hash) AS header_order_shared_count
|
||||
uniqExact(h.header_order_hash) OVER (PARTITION BY a.src_ip) AS distinct_header_orders
|
||||
```
|
||||
|
||||
Comme `h.header_order_hash` est **NULL pour toutes les lignes** (problème 1 ci-dessus), la `PARTITION BY NULL` regroupe **toutes les lignes dans une seule partition** → `count()` retourne le total de toutes les lignes pour chaque IP.
|
||||
|
||||
### Correction ✅ (auto-résolue avec Problème 1)
|
||||
|
||||
Ce problème s'est résolu automatiquement une fois la MV `mv_agg_header_fingerprint_1h` recréée. `header_order_hash` est désormais non-NULL, les partitions de window functions sont correctement calculées par hash d'ordre d'en-têtes.
|
||||
|
||||
---
|
||||
|
||||
## Problème 3 — `orphan_ratio` absent pour le trafic corrélé TCP
|
||||
|
||||
### Symptôme
|
||||
|
||||
`orphan_ratio` = 0 pour **toutes les lignes avec `correlated = 1`** (trafic TCP enrichi).
|
||||
|
||||
### Cause
|
||||
|
||||
La colonne `orphan_count` dans `mabase_prod.agg_host_ip_ja4_1h` est calculée par la MV `mv_agg_host_ip_ja4_1h` :
|
||||
|
||||
```sql
|
||||
sum(IF(src.orphan_side = 'A' OR src.correlated = 0, 1, 0)) AS orphan_count
|
||||
```
|
||||
|
||||
Pour les connexions `correlated=1`, `correlated = 0` est toujours faux, et `orphan_side = 'A'` n'est jamais vrai pour le trafic corrélé → `orphan_count = 0` systématiquement.
|
||||
|
||||
**C'est un comportement intentionnel** : les connexions TCP corrélées ont une réponse confirmée, donc elles ne sont pas des requêtes orphelines par définition.
|
||||
|
||||
### Statut
|
||||
|
||||
Pas d'action requise. La feature reste exclue automatiquement par A7 pour le modèle `Complet` (correlated=1).
|
||||
|
||||
---
|
||||
|
||||
## Problème 4 — Features à 0 persistantes dans le modèle Applicatif
|
||||
|
||||
### Symptôme (post-correction)
|
||||
|
||||
Depuis le 2026-03-17 13:05, le modèle `Applicatif` (trafic non-corrélé) signale encore ces features à 0 :
|
||||
|
||||
- `request_size_variance`
|
||||
- `mss_mobile_mismatch`
|
||||
- `is_rare_ja4`
|
||||
|
||||
### Cause
|
||||
|
||||
Ces features sont calculées depuis des colonnes L4/TCP qui sont **absent ou non-pertinentes pour le trafic applicatif pur** (`correlated=0`) :
|
||||
|
||||
| Feature | Cause |
|
||||
|---------|-------|
|
||||
| `request_size_variance` | `varPopMerge(total_ip_length_var)` — variance de longueur IP ; trafic non-corrélé = pas de données IP brutes fiables |
|
||||
| `mss_mobile_mismatch` | Dépend de `tcp_meta_mss` et `modern_browser_score` — MSS non fiable sans corrélation TCP |
|
||||
| `is_rare_ja4` | `sum(hits) OVER (PARTITION BY ja4) < 100` — dans la fenêtre Applicatif (1h, trafic réduit), tous les JA4 sont rares |
|
||||
|
||||
### Impact
|
||||
|
||||
Faible — ces features sont exclues automatiquement (A7). Elles ne dégradent pas le modèle.
|
||||
|
||||
---
|
||||
|
||||
## Impact sur le modèle IA
|
||||
|
||||
| Feature | Impact si absente | Statut |
|
||||
|---------|-------------------|--------|
|
||||
| `header_count` | Perte d'un signal fort : bots envoient souvent peu d'en-têtes | ✅ Corrigé |
|
||||
| `has_accept_language` | Perte de détection des bots sans localisation | ✅ Corrigé |
|
||||
| `has_cookie` | Perte de détection des sessions sans état | ✅ Corrigé |
|
||||
| `has_referer` | Perte du signal de navigation directe | ✅ Corrigé |
|
||||
| `modern_browser_score` | Perte du score composite de conformité navigateur | ✅ Corrigé |
|
||||
| `ua_ch_mismatch` | Perte de détection des fausses déclarations UA | ✅ Corrigé |
|
||||
| `header_order_shared_count` | Perte de la détection de fingerprints d'en-têtes partagés | ✅ Corrigé |
|
||||
| `orphan_ratio` | Signal faible pour trafic corrélé | ℹ️ By design |
|
||||
| `request_size_variance` | Signal L4 faible pour Applicatif | ℹ️ Normal |
|
||||
| `mss_mobile_mismatch` | Signal TCP faible pour Applicatif | ℹ️ Normal |
|
||||
|
||||
---
|
||||
|
||||
## Vérification post-correction
|
||||
|
||||
Cycle du 2026-03-17 13:05 — résultat observé :
|
||||
|
||||
```
|
||||
[Complet] Features à 0 : ['orphan_ratio'] ← by design ✅
|
||||
[Applicatif] Features à 0 : ['request_size_variance', 'mss_mobile_mismatch', 'is_rare_ja4'] ← normales ✅
|
||||
[Applicatif] Features non-discriminantes : ['tcp_shared_count'] ← agrégat global résiduel
|
||||
```
|
||||
|
||||
Les **10 features header** (`header_count`, `has_accept_language`, `has_cookie`, `has_referer`, `modern_browser_score`, `ua_ch_mismatch`, `header_order_shared_count`, `distinct_header_orders`, `header_order_confidence`, `mss_mobile_mismatch` pour Complet) **ne sont plus dans les warnings**. Le pipeline est opérationnel.
|
||||
Reference in New Issue
Block a user