fix(ja4ebpf): split bpf2go generate into Ja4Tc + Ja4Ssl, fix RPM systemd-rpm-macros

- Use two separate //go:generate directives (Ja4Tc for tc_capture.c, Ja4Ssl
  for uprobe_ssl.c) to avoid duplicate LICENSE symbol and multi-file clang issue
- Update loader.go to hold tcObjs/sslObjs separately with correct field names:
  UprobeSslSetFd, UprobeSslReadEntry, UretprobeSslReadExit,
  KprobeAccept4Entry, KretprobeAccept4Exit
- Add systemd-rpm-macros to all three RPM build stages (el8/el9/el10)
  so that %{_unitdir} macro resolves correctly
- RPMs now build successfully for el8, el9, el10

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-11 23:21:11 +02:00
parent a1e4c1dad5
commit 3b047b680a
155 changed files with 197011 additions and 599 deletions

View File

@ -23,9 +23,9 @@
| Composant thèse | Statut | Détail |
|-----------------|--------|--------|
| Pipeline L3-L5 (ja4sentinel) | ✅ | TTL, IP-ID, DF, TCP win/mss/scale, JA4/JA3, ALPN, SNI |
| Pipeline L7 (mod_reqin_log) | ✅ | Headers, méthode, path, query, timestamps ns |
| Corrélation (logcorrelator) | ✅ | Clé `src_ip:src_port`, Keep-Alive, orphelins |
| Pipeline L3-L5 (ja4ebpf TC ingress) | ✅ | TTL, IP-ID, DF, TCP win/mss/scale, JA4/JA3, ALPN, SNI via hooks TC + kprobe accept4 |
| Pipeline L7 (ja4ebpf uprobe SSL_read) | ✅ | Headers, méthode, path, query, timestamps ns — HTTP/1.1 et HTTP/2 via uprobe SSL_read |
| Corrélation (ja4ebpf in-memory) | ✅ | Clé `src_ip:src_port`, 256 shards, Keep-Alive, orphelins, Slowloris 10s |
| Enrichissement ASN | ✅ | `dict_iplocate_asn` (714K CIDRs, 4 colonnes) |
| Enrichissement Anubis | ✅ | Simplifié à `COALESCE(IP, ASN)` — 2 dictionnaires (`dict_anubis_ip` IP_TRIE, `dict_anubis_asn`) |
| Agrégation 1h | ✅ | 6 tables : `agg_host_ip_ja4_1h`, `agg_header_fingerprint_1h`, `agg_ip_behavior_1h`, `agg_request_timing_1h`, `agg_path_sequences_1h`, `agg_resource_cascade_1h` |
@ -132,7 +132,7 @@
| §5.3 Request Cadence Fingerprint | ✅ | `cadence_cv`, `burst_ratio`, `lag1_autocorrelation`, `benford_deviation`, `pause_ratio` dans `agg_request_timing_1h` |
| §5.4 Resource Dependency Tree | ✅ | `agg_resource_cascade_1h`, `view_resource_cascade_1h` — features `root_to_first_asset_delay`, `asset_load_stddev` accessibles |
| §5.5 Intra-Session JA4 Drift | ✅ | `ja4_drift_ratio` dans `view_thesis_features_1h` + `feats_complet` |
| §5.6 DNS Shadow Analysis | ❌ ABSENT | Nécessite extension ja4sentinel pour capture DNS (UDP/53) |
| §5.6 DNS Shadow Analysis | ❌ ABSENT | Nécessite extension ja4ebpf pour capture DNS (UDP/53) |
| §5.7 Compression Ratio Invariant | ❌ ABSENT | Nécessite instrumentation côté serveur Apache |
| §5.8 Cross-Domain Session Linking | ✅ | `host_diversity`, `host_sweep_speed`, `host_coverage_uniformity` + `cross_domain_path_similarity` (Jaccard) dans `view_thesis_features_1h` |
@ -293,7 +293,7 @@ Le module `api.py` expose **37 endpoints JSON** couvrant l'ensemble des besoins
| Priorité | Gap | Impact | Remarque |
|----------|-----|--------|----------|
| P2 🟡 | §5.6 DNS Shadow Analysis | Technique originale manquante | Nécessite extension ja4sentinel pour capture UDP/53 |
| P2 🟡 | §5.6 DNS Shadow Analysis | Technique originale manquante | Nécessite extension ja4ebpf pour capture UDP/53 |
| P2 🟡 | §5.7 Compression Ratio Invariant | Technique originale manquante | Nécessite instrumentation côté serveur Apache |
| P3 ⚪ | Authentification dashboard | Sécurité opérationnelle | Non exigé par la thèse — environnement SOC intranet |
| P3 ⚪ | CSRF sur `/api/classify` | Sécurité opérationnelle | Mitigé en déploiement restreint |
@ -319,11 +319,11 @@ Le module `api.py` expose **37 endpoints JSON** couvrant l'ensemble des besoins
- **`deploy_schema.sh`** : déploiement automatisé avec substitution des noms de bases depuis les variables d'environnement (`CLICKHOUSE_DB_LOGS`, `CLICKHOUSE_DB_PROCESSING`)
- **Dual database** : `ja4_logs` (logs bruts, enrichis, MV) + `ja4_processing` (agrégations, ML, vues, dictionnaires, audit)
- **7 dictionnaires** : `dict_iplocate_asn`, `dict_bot_ip`, `dict_bot_ja4`, `dict_browser_ja4`, `dict_asn_reputation`, `dict_anubis_ip`, `dict_anubis_asn`
- **Migrations post-déploiement** : `services/correlator/sql/migrations/` (ALTER TABLE pour déploiements existants)
- **Migrations post-déploiement** : `shared/clickhouse/` (ALTER TABLE pour déploiements existants)
### E3. Pipeline de build et tests
- **Docker-first** : chaque service dispose de `Dockerfile` (prod), `Dockerfile.dev` ou `Dockerfile.tests` (tests), et `Dockerfile.package` (RPM) pour les services Go/C
- **Tests d'intégration** : suite complète en 8 phases (build → start → schema → traffic → pipeline → dashboard → bot-detector → sentinel) via `make test-integration`
- **Couverture** : tests Go (80% gate pour le correlator), tests Python (pytest pour bot-detector, dashboard, ja4_common), tests C (cmocka pour mod_reqin_log)
- **Tests d'intégration** : suite complète en 8 phases (build → start → schema → traffic → pipeline → dashboard → bot-detector → ja4ebpf) via `make test-integration`
- **Couverture** : tests Go (ja4ebpf), tests Python (pytest pour bot-detector, dashboard, ja4_common)
- **RPM packaging** : 3 distributions (el8/el9/el10) via Rocky Linux / AlmaLinux

View File

@ -53,7 +53,7 @@ Ce document présente une architecture complète de détection et classification
- 3.3 Couche L4 — TCP
- 3.4 Couche L5 — TLS
- 3.5 Couche L7 — HTTP
- 3.6 Corrélation inter-couches (logcorrelator)
- 3.6 Corrélation inter-couches (ja4ebpf)
- 3.7 Agrégation temporelle et features dérivées
- 3.8 Détection ML semi-supervisée (full pipeline)
@ -112,7 +112,7 @@ La quatrième génération en émergence combine : analyse corrélée en temps r
Ce document décrit une architecture opérationnelle positionnée à la frontière Gen3/Gen4, déployée en production sur un serveur Apache. Les contributions techniques originales incluent :
1. **Corrélation TCP/TLS/HTTP** en temps réel via le module logcorrelator (clé : `src_ip:src_port`, timeout orphelin 500 ms)
1. **Corrélation TCP/TLS/HTTP** en temps réel via ja4ebpf (clé : `src_ip:src_port`, 256 shards, timeout orphelin 500 ms)
2. **Fingerprinting HTTP/2 passif** : extraction des trames SETTINGS, WINDOW_UPDATE, PRIORITY et de l'ordre des pseudo-headers directement depuis le stream TCP
3. **Architecture EIF bifurquée** : modèle complet (≈ 45 features L3→L7) et modèle applicatif (≈ 35 features L7 uniquement), évitant le biais de zérotage sur le trafic non corrélé
4. **Ensemble triple-voix + MetaLearner** : fusion EIF + AE + XGBoost avec régression logistique apprise sur étiquettes accumulées
@ -461,10 +461,10 @@ Résultat : les coupes ne sont plus liées aux axes, éliminant les artéfacts g
Deux modèles EIF s'exécutent en parallèle à chaque cycle de 300 secondes :
- **Modèle Complet** (≈ 45 features, L3→L7) : appliqué sur les sessions corrélées (correlated=1) pour lesquelles ja4sentinel a pu corréler les métadonnées TCP/TLS avec la requête HTTP. Inclut toutes les features des familles F1F7.
- **Modèle Complet** (≈ 45 features, L3→L7) : appliqué sur les sessions corrélées (correlated=1) pour lesquelles ja4ebpf a pu corréler les métadonnées TCP/TLS avec la requête HTTP. Inclut toutes les features des familles F1F7.
- **Modèle Applicatif** (≈ 35 features, L7 uniquement) : appliqué sur les sessions non corrélées (correlated=0) — trafic passant par un CDN ou proxy qui ne permet pas la corrélation TCP/TLS. Zeroed les features TCP/TLS pour ce modèle éviterait d'introduire un biais systématique de zéro-imputation.
La bifurcation est justifiée par le fait que les features TCP/TLS ne sont disponibles que lorsque ja4sentinel a corrélé la connexion réseau avec la requête HTTP. Imputer ces features à zéro pour le trafic non corrélé créerait un signal artificiel (zéro n'est pas neutre pour un EIF — il est interprété comme une valeur réelle).
La bifurcation est justifiée par le fait que les features TCP/TLS ne sont disponibles que lorsque ja4ebpf a corrélé la connexion réseau avec la requête HTTP. Imputer ces features à zéro pour le trafic non corrélé créerait un signal artificiel (zéro n'est pas neutre pour un EIF — il est interprété comme une valeur réelle).
#### 2.4.2b Autoencoders (AE) et détection d'anomalies
@ -799,18 +799,18 @@ httpcloak est un outil d'évasion qui tente d'imiter l'empreinte TLS de Chrome.
┌──────────────────────────────────────────────────────────────────┐
│ SOURCES DE DONNÉES │
├───────────────────────────┬──────────────────────────────────────┤
ja4sentinel │ mod_reqin_log
(Source B) │ (Source A)
TC ingress (XDP/TC) │ uprobe SSL_read
Couches L3/L4/L5 │ Couche L7 HTTP déchiffré
│ │ │
libpcap, CAP_NET_RAW │ Module Apache personnalisé
Couches L3/L4/L5 │ Couches L7 HTTP + L5 HTTP/2
│ - IP header (TTL, DF) │ - src_ip, src_port
- TCP SYN (MSS, Window, │ - timestamp_ns (nanoseconde)
Options, Scale) │ - méthode, chemin, query
- TLS ClientHello │ - version HTTP
(JA4, JA4T, ALPN, SNI) │ - en-têtes (bruts + ordre)
│ │ - statut, taille, durée_ms
│ │ - HTTP/2 preface passif :
réseau XDP/TC → │ Go Magic Bytes dispatcher →
│ - SYN : TTL, IP-ID, DF, HTTP/1.1 : method, path, query,
MSS, Window, Scale │ headers (bruts + ordre),
- TLS ClientHello : │ status, taille, durée_ms,
JA4, ALPN, SNI │ timestamp_ns (nanoseconde)
- HTTP port 80/8080 │ HTTP/2 (depuis preface client) :
│ │ ordre pseudo-headers
│ │ WINDOW_UPDATE, PRIORITY flag,
│ │ SETTINGS (7 params individuels), │
│ │ WINDOW_UPDATE, PRIORITY flag, │
│ │ ordre pseudo-headers │
@ -819,8 +819,8 @@ httpcloak est un outil d'évasion qui tente d'imiter l'empreinte TLS de Chrome.
└─────────────┬─────────────┘
┌─────────▼─────────┐
logcorrelator
Corrélation in-memory
(ja4ebpf)
│ Clé: src_ip:port │
│ Keep-Alive multi-│
│ request tracking │
@ -899,7 +899,7 @@ Features : `avg_ttl` (TTL moyen sur la fenêtre), `ttl_std` (écart-type du TTL
### 3.3 Couche L4 — TCP
**TCP Keep-Alive et multiplexage HTTP** : à ne pas confondre avec HTTP Keep-Alive (`Connection: keep-alive`). Le TCP Keep-Alive est un mécanisme de détection de connexions mortes au niveau du noyau (envoi de paquets ACK vides après inactivité). L'HTTP Keep-Alive, en revanche, maintient la connexion TCP ouverte pour réutilisation par plusieurs requêtes HTTP successives. Le module mod_reqin_log trace le nombre de requêtes HTTP dans chaque connexion TCP via `max_keepalives`.
**TCP Keep-Alive et multiplexage HTTP** : à ne pas confondre avec HTTP Keep-Alive (`Connection: keep-alive`). Le TCP Keep-Alive est un mécanisme de détection de connexions mortes au niveau du noyau (envoi de paquets ACK vides après inactivité). L'HTTP Keep-Alive, en revanche, maintient la connexion TCP ouverte pour réutilisation par plusieurs requêtes HTTP successives. ja4ebpf trace le nombre de requêtes HTTP dans chaque connexion TCP via `max_keepalives` dans le gestionnaire de corrélation in-memory.
**Coefficient de variation (CV)** : mesure adimensionnelle de variabilité, CV = σ/μ. Un CV ≈ 0 indique une régularité élevée (automatisation à timer fixe) ; un CV ≈ 13 indique une variabilité naturelle (humain). Applicable à `syn_timing_cv` (variabilité du délai SYN→ClientHello) et à `cadence_cv` (variabilité des intervalles inter-requêtes).
@ -932,18 +932,18 @@ Features : `avg_ttl` (TTL moyen sur la fenêtre), `ttl_std` (écart-type du TTL
### 3.5 Couche L7 — HTTP
La couche L7 constitue la couche la plus riche en features comportementales. Le module **mod_reqin_log** est un module Apache personnalisé qui enregistre chaque requête HTTP avec une précision nanoseconde via `clock_gettime(CLOCK_REALTIME)`.
La couche L7 constitue la couche la plus riche en features comportementales. L'agent **ja4ebpf** capture le flux HTTP déchiffré via un uprobe sur `SSL_read` (OpenSSL/BoringSSL), avec une précision nanoseconde via `bpf_ktime_get_ns()`.
Données capturées par mod_reqin_log : `src_ip`, `src_port`, `timestamp_ns` (nanoseconde absolu), `method`, `path`, `query_string`, `http_version`, `headers_raw` (en-têtes bruts dans leur ordre d'émission), `header_order_signature` (hash de l'ordre), `status_code`, `response_size`, `duration_ms`. L'horodatage nanoseconde est critique pour le calcul des features temporelles F8 (cadence_cv, lag1_autocorrelation, benford_deviation, root_to_first_asset_delay).
Données capturées par ja4ebpf : `src_ip`, `src_port`, `timestamp_ns` (nanoseconde absolu), `method`, `path`, `query_string`, `http_version`, `headers_raw` (en-têtes bruts dans leur ordre d'émission), `header_order_signature` (hash de l'ordre), `status_code`, `response_size`, `duration_ms`. L'horodatage nanoseconde est critique pour le calcul des features temporelles F8 (cadence_cv, lag1_autocorrelation, benford_deviation, root_to_first_asset_delay).
**Fingerprinting HTTP/2 passif intégré** : pour les connexions HTTP/2, mod_reqin_log capture passivement le preface client via un hook `ap_hook_process_connection` enregistré en priorité `APR_HOOK_FIRST`, exécuté avant `mod_http2`. Ce hook effectue une lecture spéculative (`AP_MODE_SPECULATIVE`) de 512 octets sur les filtres d'entrée de la connexion, qui déclenche transparentement le handshake TLS (`mod_ssl`) et retourne les données déchiffrées sans les consommer. Le preface H2 est ensuite parsé pour en extraire :
**Fingerprinting HTTP/2 passif intégré** : pour les connexions HTTP/2, ja4ebpf identifie le protocole via le Go Magic Bytes dispatcher qui reconnaît le preface `PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n` dans le flux SSL_read déchiffré. Le parser HTTP/2 extrait ensuite les frames depuis ce flux déchiffré sans aucune instrumentation côté serveur web :
- Les 7 paramètres SETTINGS individuels (IDs 16 et 8), chacun stocké dans une colonne ClickHouse dédiée (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`), avec la valeur -1 pour les paramètres absents du preface client
- L'incrément `h2_window_update` de la frame WINDOW_UPDATE sur la connexion (stream ID 0)
- Le flag `h2_has_priority` indiquant la présence d'un champ PRIORITY dans la frame HEADERS
- L'ordre des pseudo-headers `h2_pseudo_order` (ex. `m,a,s,p`) extrait par décodage HPACK partiel de la première frame HEADERS
- Le fingerprint composite `h2_fingerprint` au format Akamai et la chaîne brute `h2_settings_fp`
Les données H2 sont stockées dans les notes de la connexion primaire (`c->notes`). Pour les connexions HTTP/2, `mod_http2` crée des connexions secondaires (c2) par stream ; le hook `log_request` accède aux notes H2 via `r->connection->master` (connexion primaire c1). Le hook retourne `DECLINED` après stockage, laissant `mod_http2` gérer la suite normalement.
Les données H2 sont associées à la session courante via la clé `src_ip:src_port` dans le gestionnaire de corrélation in-memory de ja4ebpf, puis transmises en batch à ClickHouse avec les données TCP/TLS correspondantes.
**Colonnes HTTP/2 dans `ja4_logs.http_logs`** :
@ -966,11 +966,11 @@ La convention `-1` pour les paramètres SETTINGS absents est essentielle : elle
Toutes les features des familles F1F6 et F8 proviennent de cette couche, agrégées sur des fenêtres temporelles de 300 secondes (5 minutes) par session (src_ip).
### 3.6 Corrélation inter-couches (logcorrelator)
### 3.6 Corrélation inter-couches (ja4ebpf)
**Clé de corrélation** : `(src_ip, src_port)` — le tuple source identifie de manière unique une connexion TCP à un instant donné (une connexion TCP est identifiée par le 4-tuple src_ip:src_port:dst_ip:dst_port, mais dst_ip:dst_port étant fixes pour un serveur, le 2-tuple src suffit).
**Gestion du HTTP Keep-Alive** : une connexion TCP peut transporter plusieurs requêtes HTTP successives. logcorrelator maintient une table de connexions indexée par `(src_ip, src_port)`. Chaque requête HTTP reçue de mod_reqin_log est associée à l'enregistrement TCP/TLS ouvert correspondant, et `max_keepalives` est incrémenté. L'enregistrement reste ouvert jusqu'à la déconnexion TCP ou l'expiration.
**Gestion du HTTP Keep-Alive** : une connexion TCP peut transporter plusieurs requêtes HTTP successives. ja4ebpf maintient un gestionnaire de corrélation in-memory organisé en 256 shards (partitionnement par hash de src_ip pour éviter la contention). Chaque requête HTTP capturée via l'uprobe SSL_read est associée à l'enregistrement TCP/TLS ouvert correspondant, et `max_keepalives` est incrémenté. Un GC toutes les 100 ms libère les sessions expirées.
**Timeout orphelin** : si aucun enregistrement réseau ne correspond à une requête HTTP dans les 500 ms, la requête est enregistrée comme orpheline (`correlated=0`). Cela se produit quand le trafic arrive via un CDN ou un proxy inverse qui établit une nouvelle connexion TCP entre le proxy et le serveur, rendant l'adresse source TCP celle du proxy plutôt que celle du client original.
@ -1099,9 +1099,7 @@ Le système `browser_confidence` à 6 axes (§3.8) fournit un score agrégé uti
- **Compression d'en-têtes HPACK** : [RFC 7541](https://www.rfc-editor.org/rfc/rfc7541) définit HPACK, une compression d'en-têtes HTTP par table d'indexation. L'ordre des entrées dans la table statique HPACK est normalisé, mais l'ordre de sérialisation des pseudo-headers est laissé à l'implémentation.
- **Contrôle de flux** : mécanisme de fenêtres (`WINDOW_UPDATE`) limitant le débit pour éviter la saturation du récepteur.
Après terminaison TLS par Apache (`mod_ssl`), le flux HTTP/2 est déchiffré et disponible en clair. Le fingerprinting passif est réalisé directement par **mod_reqin_log** via un hook `process_connection` enregistré en priorité `APR_HOOK_FIRST`. Ce hook s'exécute **avant** `mod_http2` et effectue une lecture spéculative (`AP_MODE_SPECULATIVE`, 512 octets) sur `c->input_filters`, déclenchant transparentement le handshake TLS. Si les données commencent par le magic HTTP/2 (`PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`), le parser binaire extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS du preface client, stocke les résultats dans `c->notes`, puis retourne `DECLINED` pour laisser `mod_http2` rer la connexion. Cette approche ne consomme pas les données et n'interfère pas avec le traitement HTTP/2 normal.
**Note architecturale** : l'approche par filtre de connexion (`AP_FTYPE_CONNECTION`) a été abandonnée car `mod_http2` prend le contrôle complet de la connexion via son propre hook `process_connection` les filtres d'entrée de connexion ne sont jamais invoqués pour les connexions HTTP/2. Le hook `process_connection` à priorité haute est la seule technique fiable pour intercepter le preface H2 avant `mod_http2`.
Après terminaison TLS par le serveur web, le flux HTTP/2 est déchiffré et disponible en clair dans le contexte d'OpenSSL/BoringSSL. Le fingerprinting passif est réalisé par **ja4ebpf** via un uprobe sur `SSL_read`. Lorsque le Go Magic Bytes dispatcher détecte le magic HTTP/2 (`PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n`) dans le flux déchiffré, le parser H2 extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS du preface client, sans modifier ni interrompre le flux. Cette approche est agnostique au serveur web (Apache, Nginx, Varnish, HAProxy) et ne nécessite aucun module natif installé côté serveur.
#### Frame SETTINGS
@ -1397,7 +1395,7 @@ Le module `browser_matcher` génère les features suivantes dans le vecteur feat
| `h2_pseudo_order` | String | Ordre observé (ex. `m,a,s,p`) | `[impl.]` (colonne `h2_pseudo_order` dans `http_logs`) |
| `tls_h2_family_mismatch` | UInt8 | 1 si JA4 dit Chrome mais H2 SETTINGS dit Firefox/outil | `[impl.]` (feature F4) |
Les features `h2_window_update_value`, `h2_has_priority_frames` et `h2_pseudo_order` sont désormais capturées par mod_reqin_log et stockées dans des colonnes individuelles de `ja4_logs.http_logs`. De plus, chaque paramètre SETTINGS HTTP/2 dispose de sa propre colonne (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec la valeur -1 pour les paramètres absents du preface client. La feature `tls_h2_family_mismatch` est implémentée dans le vecteur feature global (famille F4 TLS features) et se calcule à partir des données JA4 existantes et des colonnes H2 individuelles disponibles dans `ja4_logs.http_logs`. Les features `browser_match_*` requièrent l'exécution complète du module `browser_matcher.py`.
Les features `h2_window_update_value`, `h2_has_priority_frames` et `h2_pseudo_order` sont désormais capturées par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré et stockées dans des colonnes individuelles de `ja4_logs.http_logs`. De plus, chaque paramètre SETTINGS HTTP/2 dispose de sa propre colonne (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec la valeur -1 pour les paramètres absents du preface client. La feature `tls_h2_family_mismatch` est implémentée dans le vecteur feature global (famille F4 TLS features) et se calcule à partir des données JA4 existantes et des colonnes H2 individuelles disponibles dans `ja4_logs.http_logs`. Les features `browser_match_*` requièrent l'exécution complète du module `browser_matcher.py`.
### 3.9.5 Maintenance des signatures
@ -1508,7 +1506,7 @@ La famille 1 capture les signaux de volume et de cadence bruts. Ces features son
| Scraper simple (connexion par req) | 1 |
| Bot pipeline agressif | > 100 |
**Implémentation** : mod_reqin_log incrémente un compteur par connexion TCP (`src_ip:src_port`) à chaque requête HTTP reçue. Le maximum sur la session est agrégé dans `view_ai_features_1h`.
**Implémentation** : ja4ebpf incrémente un compteur via le gestionnaire de corrélation in-memory par connexion TCP (`src_ip:src_port`) à chaque requête HTTP reçue. Le maximum sur la session est agrégé dans `view_ai_features_1h`.
#### count_login_post `[impl.]`
@ -2637,7 +2635,7 @@ Cette asymétrie constitue un signal exploitable : un flux HTTP sans résolution
#### Technique : ratio DNS shadow
Capture DNS passive via `ja4sentinel` étendu au port UDP/53, puis corrélation avec les flux HTTP :
Capture DNS passive via `ja4ebpf` étendu au port UDP/53, puis corrélation avec les flux HTTP :
```
dns_shadow_ratio = requêtes HTTP vers hôte X / résolutions DNS de l'hôte X observées
@ -2658,7 +2656,7 @@ Les bots utilisant DoH contournent l'analyse DNS shadow. Cependant, les connexio
Le fingerprinting **JA4D/JA4D6** des requêtes DHCP/DHCPv6 pourrait compléter l'analyse en identifiant les dispositifs derrière NAT, ajoutant une couche d'identification au-delà de l'adresse IP. Cette extension est envisagée comme travail futur conjoint à DNS Shadow Analysis.
**`[todo]` Non implémenté** : nécessite l'extension de `ja4sentinel` pour la capture UDP/53. Travail futur priorité 1 (voir §6.6).
**`[todo]` Non implémenté** : nécessite l'extension de `ja4ebpf` pour la capture UDP/53. Travail futur priorité 1 (voir §6.6).
---
@ -2946,13 +2944,13 @@ Un seul utilisateur réel alterne quelques connexions (26 ports source actifs
**browser_matcher complet (§3.9)** :
- État actuel : modules `[partiel]` (logique de score partielle, base de signatures incomplète)
- Données H2 brutes : `[impl.]` — capture des 7 paramètres SETTINGS individuels, WINDOW_UPDATE, flag PRIORITY et ordre pseudo-headers par mod_reqin_log via hook `process_connection` (APR_HOOK_FIRST, AP_MODE_SPECULATIVE). Colonnes individuelles dans `ja4_logs.http_logs` (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`, `h2_window_update`, `h2_has_priority`, `h2_pseudo_order`)
- Données H2 brutes : `[impl.]` — capture des 7 paramètres SETTINGS individuels, WINDOW_UPDATE, flag PRIORITY et ordre pseudo-headers par ja4ebpf via le parser HTTP/2 du flux SSL_read déchiffré. Colonnes individuelles dans `ja4_logs.http_logs` (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`, `h2_window_update`, `h2_has_priority`, `h2_pseudo_order`)
- Travail restant : compléter `browser_signatures.py` avec signatures Firefox et Safari complètes, implémenter le rechargement ClickHouse toutes les 24h, intégrer les 5 features `browser_match_*` dérivées dans le vecteur feature global
- Dépendances : ~~capture des frames H2 SETTINGS~~ (réalisé) ; reste le module `browser_matcher.py`
**DNS Shadow Analysis (§5.6)** :
- État actuel : `[todo]` non implémenté
- Travail requis : extension de `ja4sentinel` pour capture UDP/53 + TCP/53, table ClickHouse `dns_resolutions`, corrélation avec table `sessions`
- Travail requis : extension de `ja4ebpf` pour capture UDP/53 + TCP/53, table ClickHouse `dns_resolutions`, corrélation avec table `sessions`
- Contraintes techniques : nécessite accès privilégié au trafic réseau niveau interface (pcap ou eBPF)
- Estimation : 68 semaines de développement + tests de charge
@ -3016,7 +3014,7 @@ L'**explainabilité** est assurée par ExIFFI ([Frizzo et al., 2024](https://arx
Extension du système `browser_confidence` à 6 axes vers une correspondance structurée par famille de navigateur, fondée sur l'analyse passive de 7 dimensions H2 : frame SETTINGS (7 paramètres), WINDOW_UPDATE, ordre des pseudo-headers, frames PRIORITY, cohérence des en-têtes HTTP, structure TLS, et lookup JA4 par dictionnaire.
La capture passive est réalisée par mod_reqin_log via un hook `ap_hook_process_connection` (APR_HOOK_FIRST) qui intercepte le preface HTTP/2 avant `mod_http2` par lecture spéculative. Chaque paramètre SETTINGS est stocké dans une colonne ClickHouse individuelle (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec -1 pour les paramètres absents, complétés par `h2_window_update`, `h2_has_priority` et `h2_pseudo_order`.
La capture passive est réalisée par ja4ebpf via un uprobe sur `SSL_read` (OpenSSL/BoringSSL). Le Go Magic Bytes dispatcher identifie le preface HTTP/2 dans le flux déchiffré, et le parser H2 extrait les frames SETTINGS, WINDOW_UPDATE et HEADERS sans instrumentation serveur. Chaque paramètre SETTINGS est stocké dans une colonne ClickHouse individuelle (`h2_header_table_size`, `h2_enable_push`, `h2_max_concurrent_streams`, `h2_initial_window_size`, `h2_max_frame_size`, `h2_max_header_list_size`, `h2_enable_connect_protocol`) avec -1 pour les paramètres absents, complétés par `h2_window_update`, `h2_has_priority` et `h2_pseudo_order`.
Cette technique permet de détecter des outils d'évasion qui reproduisent correctement la couche TLS (curl_cffi, httpcloak) mais échouent à reproduire les subtilités H2 — notamment l'ordre des pseudo-headers et la valeur WINDOW_UPDATE.
@ -3058,7 +3056,7 @@ Architecture de données fondée sur ClickHouse avec **AggregatingMergeTree view
| F11 — Futures (non impl.) | dns_shadow_ratio, compression_ratio_invariant (2 features) | 0 | 0 | 2 |
| **Total** | **85** | **70** | **5** | **2** + meta |
**Résumé quantitatif** : ~82 % des features entièrement implémentées (`[impl.]`), ~6 % partiellement implémentées (`[partiel]`, les 5 features `browser_match_*` de browser_matcher), ~2 % non implémentées (`[todo]`, §5.6 et §5.7). Les 3 features H2 brutes (`h2_window_update_value`, `h2_has_priority_frames`, `h2_pseudo_order`) sont passées de `[partiel]` à `[impl.]` suite à l'intégration de la capture HTTP/2 passive dans mod_reqin_log.
**Résumé quantitatif** : ~82 % des features entièrement implémentées (`[impl.]`), ~6 % partiellement implémentées (`[partiel]`, les 5 features `browser_match_*` de browser_matcher), ~2 % non implémentées (`[todo]`, §5.6 et §5.7). Les 3 features H2 brutes (`h2_window_update_value`, `h2_has_priority_frames`, `h2_pseudo_order`) sont passées de `[partiel]` à `[impl.]` suite à l'intégration de la capture HTTP/2 passive dans ja4ebpf via le parser HTTP/2 du flux SSL_read.
### Perspective

View File

@ -5,30 +5,25 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp
## Architecture système
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
Serveur Linux cible (Apache) │
┌─────────────────┐ HTTP req ┌───────────────────────┐ │
│ │ Client │────────────▶│ Apache HTTPD
(navigateur / + mod-reqin-log (C) │
│ │ bot) │ └───────────┬───────────┘
│ │ │ │ JSON / UNIX DGRAM
│ /var/run/logcorrelator/http.socket │
│ ▼ │
│ │ │ TLS CH ┌──────────────────────────────────────┐
│ │ │────────────▶│ sentinel (Go · libpcap) │
└─────────────────┘ (pcap) │ → JA4/JA3 fingerprints │ │
└───────────┬──────────────────────────┘ │
│ JSON / UNIX DGRAM
│ │ /var/run/logcorrelator/network.socket│
│ ▼
┌───────────────────────────────┐
│ │ correlator (Go · hex. arch) │ │
│ │ join src_ip:src_port + TTL │ │
│ └───────────┬───────────────────┘ │
│ │ │
└───────────────────────────────────────────────┼─────────────────────────────────────┘
│ INSERT (Native TCP :9000)
+---------------------------------------------------+
| Serveur Linux cible (Apache/Nginx/...) |
| |
| Client --HTTPS (443)--> [Serveur web] |
| Client --HTTP (80)---> [Serveur web] |
| |
| +-----------------+ |
| | ja4ebpf | (s'exécute sur le meme host)|
| | (eBPF CO-RE) | |
| | TC ingress |<-- L3/L4/L5 (SYN, TLS CH) |
| | uprobe SSL_read|<-- L7 HTTPS (déchiffré) |
| | kprobe tcp_recv|<-- L7 HTTP port 80/8080 |
| | 256-shard mgr | corrélation src_ip:src_port |
| +--------+--------+ |
| | |
+-----------|-----------------------------------+ |
| INSERT batch | |
v v
INSERT (Native TCP :9000)
┌───────────────────────────────────────────────────────┐
│ ClickHouse 24.8 │
@ -71,15 +66,17 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp
### Phase 1 — Capture
1. **mod-reqin-log** (module Apache C11) intercepte chaque requête HTTP dans le hook `post_read_request`. Il sérialise method, path, headers, client IP/port en JSON et envoie le datagramme vers `/var/run/logcorrelator/http.socket`.
1. **ja4ebpf TC ingress hook** capture les paquets réseau bruts. Pour chaque TCP SYN : `src_ip`, `src_port`, `ttl`, `window_size`, `mss`, `window_scale`, `df_bit`. Pour chaque TLS ClientHello : décodage du payload pour extraire `ciphers`, `extensions`, `elliptic_curves`, `alpn`, `sni` (calcul du hash JA4/JA3 en espace utilisateur Go).
2. **sentinel** (démon Go) capture les paquets TLS ClientHello via libpcap sur les ports configurés (défaut : 443, 8443). Il extrait les métadonnées IP/TCP, génère les empreintes JA4 et JA3, et envoie le résultat en JSON vers `/var/run/logcorrelator/network.socket`.
2. **ja4ebpf uprobe SSL_read** accroche `SSL_read` dans la bibliothèque OpenSSL/BoringSSL du serveur web. Les données déchiffrées sont écrites dans un RingBuffer eBPF. Un kprobe sur `accept4` fournit la correspondance `fd → src_ip:src_port` pour annoter chaque buffer L7.
### Phase 2 — Corrélation
3. **ja4ebpf kprobe tcp_recvmsg** (HTTP port 80/8080) intercepte le payload TCP avant consommation par le serveur pour les connexions non chiffrées.
3. **correlator** (démon Go, architecture hexagonale) écoute les deux sockets Unix. Il met en tampon les événements entrants et les corrèle par `src_ip:src_port` dans une fenêtre temporelle configurable (défaut : 10 s). Le mode `one_to_many` (Keep-Alive) permet de réutiliser un seul handshake TLS (source B) pour plusieurs requêtes HTTP (source A). Les événements corrélés fusionnent les champs HTTP + TLS en un objet `CorrelatedLog` JSON, inséré dans **`ja4_logs.http_logs_raw`**.
### Phase 2 — Corrélation en mémoire
### Phase 3 — Enrichissement (ClickHouse)
4. **ja4ebpf 256-shard manager** (espace utilisateur Go) consomme les trois RingBuffers eBPF via des goroutines dédiées. Les événements L3/L4/L5 et L7 sont corrélés par `src_ip:src_port` dans une table de sessions shardée (256 shards, mutex par shard). Timeout orphelin : 500 ms (émission avec `correlated=0`). Détection Slowloris : émission partielle après 10 s. GC des sessions fantômes : toutes les 100 ms. Le dispatcher magic bytes route vers le parser HTTP/1.1 ou HTTP/2. Pour HTTP/2, la première frame SETTINGS + WINDOW_UPDATE est décodée pour le fingerprinting passif. Lobjet corrélé est inséré dans **`ja4_logs.http_logs_raw`** par batch.
### Phase 3 — Enrichissement### Phase 3 — Enrichissement (ClickHouse)
4. **mv_http_logs** (vue matérialisée) transforme le JSON de `http_logs_raw` en la table structurée `ja4_logs.http_logs`, enrichissant chaque ligne avec :
- Données ASN via `dict_iplocate_asn` (IP_TRIE)
@ -123,14 +120,12 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp
## Matrice d'interaction des composants
| De ↓ \ Vers → | mod-reqin-log | sentinel | correlator | ClickHouse | bot-detector | dashboard |
|----------------|:---:|:---:|:---:|:---:|:---:|:---:|
| **mod-reqin-log** | — | — | UNIX DGRAM (source A) | — | — | — |
| **sentinel** | — | — | UNIX DGRAM (source B) | — | — | — |
| **correlator** | — | — | — | Native TCP :9000 (INSERT) | — | — |
| **ClickHouse** | — | — | — | MVs internes | — | — |
| **bot-detector** | — | — | — | HTTP :8123 (SELECT/INSERT) | — | — |
| **dashboard** | — | — | — | HTTP :8123 (SELECT/INSERT) | — | — |
| De ↓ \ Vers → | ja4ebpf | ClickHouse | bot-detector | dashboard |
|----------------|:---:|:---:|:---:|:---:|
| **ja4ebpf** | — | Native TCP :9000 (INSERT batch) | — | — |
| **ClickHouse** | — | MVs internes | — | — |
| **bot-detector** | — | HTTP :8123 (SELECT/INSERT) | — | — |
| **dashboard** | — | HTTP :8123 (SELECT/INSERT) | — | — |
## Propriété des tables ClickHouse
@ -138,7 +133,7 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp
| Table / Vue | Écrit par | Lu par |
|-------------|-----------|--------|
| `http_logs_raw` | correlator | mv_http_logs (MV) |
| `http_logs_raw` | ja4ebpf | mv_http_logs (MV) |
| `http_logs` | mv_http_logs (MV) | mv_agg_* (6 MVs), dashboard |
| `mv_http_logs` | — (MV automatique) | — |
@ -187,17 +182,23 @@ ja4-platform est un pipeline de sécurité qui capture le trafic réseau en temp
## Algorithme de corrélation
Le correlator joint les événements HTTP (source A) avec les événements TLS/réseau (source B) via une corrélation à deux clés :
**ja4ebpf** corrèle les événements L3/L4/L5 (RingBuffer TC ingress) avec les événements L7 (RingBuffer uprobe) via une table de sessions en mémoire :
1. **Clé** : `src_ip + src_port` — l'IP source et le port éphémère du client identifient une connexion TCP de manière unique.
2. **Fenêtre temporelle** : Les événements doivent arriver dans la fenêtre configurée (défaut 10 secondes).
3. **Mode de correspondance** :
- `one_to_one` : Chaque événement B correspond à un seul événement A (consommé après correspondance).
- `one_to_many` (défaut, Keep-Alive) : Un seul B (handshake TLS) peut correspondre à plusieurs A (requêtes HTTP) sur la même connexion. Le B possède un TTL configurable (défaut 120 s) réinitialisé à chaque correspondance.
4. **Gestion des orphelins** : Les événements A sans correspondance sont émis après un délai configurable (défaut 500 ms) avec `correlated=false` et `orphan_side=A`.
1. **Clé** : `src_ip + src_port` — lIP source et le port éphémère identifient une connexion TCP de manière unique.
2. **Sharding** : 256 shards (`src_port % 256`), chacun protégé par un `sync.Mutex`. Réduit la contention sous fort trafic.
3. **Gestion Keep-Alive** : Un seul état TLS/L4 est conservé par connexion TCP et partagé entre toutes les requêtes HTTP successives. Compteur `maxkeepalives` incrémenté à chaque requête.
4. **Magic Bytes dispatcher** : Les premiers octets du buffer L7 déterminent le protocole :
- `PRI * HTTP/2.0\r\n` → parser HTTP/2 (SETTINGS + WINDOW_UPDATE + pseudo-headers)
- `GET `, `POST `, `HEAD `, etc. → parser HTTP/1.1
- Autre → `ProtoUnknown` (bruit TLS ou protocole non supporté)
5. **Orphelins** : Événements L7 sans correspondance L3/L4 dans les 500 ms → `correlated=0`.
6. **Slowloris** : Connexion TCP sans requête HTTP complète après 10 s → export partiel.
7. **GC** : Goroutine dédiée, itération sur tous les shards toutes les 100 ms.
La sortie est insérée dans **`ja4_logs.http_logs_raw`** (base `ja4_logs`), pas dans `ja4_processing`.
## Pipeline ML
## Pipeline ML — bot-detector (détail)
```
@ -253,15 +254,15 @@ Format original de fingerprinting TLS :
Le `ja3_hash` est le hash MD5 de la chaîne JA3.
Les deux empreintes sont générées par sentinel à partir du payload TLS ClientHello.
Les deux empreintes sont générées par **ja4ebpf** (espace utilisateur Go) à partir du payload TLS ClientHello capturé par le TC ingress hook.
## Stack technologique
| Composant | Technologie |
|-----------|-------------|
| Capture de paquets | Go 1.24.6 + libpcap (gopacket) |
| Logging HTTP | Module Apache C11 (APR, `apxs`) |
| Corrélation d'événements | Go 1.24.6 (architecture hexagonale) |
| Capture réseau (L3/L4/L5) | Go 1.24.6 + eBPF CO-RE (TC ingress, cilium/ebpf) |
| Capture applicative (L7) | eBPF uprobe SSL_read + kprobe tcp_recvmsg |
| Corrélation en mémoire | Go 1.24.6 (256-shard manager, goroutines) |
| Détection ML — EIF | Python 3.11 + isotree |
| Détection ML — Autoencoder | Python 3.11 + PyTorch |
| Détection ML — Supervisé | Python 3.11 + XGBoost |
@ -272,7 +273,7 @@ Les deux empreintes sont générées par sentinel à partir du payload TLS Clien
| Frontend dashboard | htmx + Chart.js + ECharts + Tailwind CSS (CDN) |
| Magasin de données | ClickHouse 24.8 (dual-database) |
| Déploiement | systemd, Docker, RPM (Rocky 8/9/10) |
| IPC | Sockets UNIX datagramme |
| IPC | RingBuffers eBPF (kernel → userspace Go) |
| Workspace Go | `go.work` (Go 1.24.6) |
## Fichiers de schéma SQL (13)

View File

@ -13,11 +13,9 @@
| Service | Runtime | Port par défaut |
|---------|---------|-----------------|
| sentinel | Go binary + libpcap (RPM) | — (capture réseau passive) |
| correlator | Go binary (RPM) | 8080 (metrics, optionnel) |
| mod-reqin-log | Apache module .so (RPM) | — (intégré à httpd) |
| ja4ebpf | Go binary + eBPF CO-RE (RPM) | — (capture réseau passive) |
| bot-detector | Python 3.11 (Docker) | 8080 (health check) |
| dashboard | Python 3.11 / FastAPI (Docker) | 8000 (API) + 3000 (frontend) |
| dashboard | Python 3.11 / FastAPI (Docker) | 8000 (API) |
---
@ -206,103 +204,64 @@ sudo chown -R clickhouse:clickhouse /var/lib/clickhouse/user_files/
---
## Étape 5 — Installation des services Go (RPM)
## Étape 5 — Installation de ja4ebpf (RPM)
### Build des RPMs
```bash
# Tous les RPMs (sentinel + correlator + mod-reqin-log) × 3 distros
make rpm-all
# Ou individuellement
make rpm-sentinel # → services/sentinel/dist/
make rpm-correlator # → services/correlator/dist/
make rpm-mod-reqin-log # → services/mod-reqin-log/dist/
# RPM ja4ebpf × 3 distros (el8, el9, el10)
make rpm-ja4ebpf
# → services/ja4ebpf/dist/rpm/el{8,9,10}/
```
Les RPMs sont générés dans `services/<nom>/dist/` avec un sous-dossier par distro (el8, el9, el10).
Le build utilise un pipeline Docker multi-étapes Rocky Linux :
1. Létape `go-builder` compile le bytecode eBPF (clang/llvm) puis le binaire Go statique
2. Les étapes `rpm-el8`, `rpm-el9`, `rpm-el10` exécutent `rpmbuild` pour chaque distro cible
3. Létape `alpine` collecte les RPMs via `--output type=local`
### Installation des RPMs
### Installation du RPM
```bash
# Sur le serveur cible (Rocky 9 par exemple)
sudo yum install -y ./ja4sentinel-*.el9.x86_64.rpm
sudo yum install -y ./logcorrelator-*.el9.x86_64.rpm
sudo yum install -y ./mod_reqin_log-*.el9.x86_64.rpm
sudo yum install -y ./ja4ebpf-*.el9.x86_64.rpm
```
### Configuration du sentinel
### Configuration de ja4ebpf
```bash
# Fichier de configuration principal
sudo cp /etc/ja4sentinel/config.yml.default /etc/ja4sentinel/config.yml
sudo vi /etc/ja4sentinel/config.yml
sudo cp /etc/ja4ebpf/config.yml.example /etc/ja4ebpf/config.yml
sudo vi /etc/ja4ebpf/config.yml
```
Variables d'environnement (dans `/etc/sysconfig/ja4sentinel` ou `.env`) :
Variables denvironnement clés (dans `/etc/sysconfig/ja4ebpf`) :
| Variable | Défaut | Description |
|----------|--------|-------------|
| `JA4EBPF_INTERFACE` | `eth0` | Interface réseau à observer |
| `JA4EBPF_CLICKHOUSE_DSN` | — | DSN ClickHouse (ex: `clickhouse://data_writer:pwd@host:9000/ja4_logs`) |
| `JA4EBPF_TARGET_BINARY` | `/usr/sbin/httpd` | Binaire OpenSSL à hooker (uprobe SSL_read) |
| `JA4EBPF_BATCH_SIZE` | `500` | Taille des batchs dinsertion ClickHouse |
| `JA4EBPF_FLUSH_INTERVAL_MS` | `200` | Intervalle de flush (ms) |
| `JA4EBPF_SESSION_TIMEOUT_MS` | `500` | Timeout orphelin (ms) |
```bash
JA4SENTINEL_INTERFACE=eth0 # Interface réseau à capturer
JA4SENTINEL_PORTS=443,8443 # Ports TLS à surveiller
sudo systemctl enable --now ja4ebpf
sudo systemctl status ja4ebpf
journalctl -u ja4ebpf -f
```
Le sentinel écrit vers le socket Unix du correlator : `/var/run/logcorrelator/network.socket`
### Capabilities Linux requises (SELinux Enforcing)
```bash
sudo systemctl enable --now ja4sentinel
sudo systemctl status ja4sentinel
journalctl -u ja4sentinel -f
```
`ja4ebpf` ne tourne **pas** en root absolu. Le fichier systemd utilise les capabilities minimales :
### Configuration du correlator
| Capability | Raison |
|------------|--------|
| `CAP_BPF` | Chargement des programmes eBPF (kernel 5.8+) |
| `CAP_SYS_ADMIN` | Requis pour les uprobes et RHEL 8 (kernel 4.18 <5.8) |
| `CAP_NET_ADMIN` | Attachement des hooks TC ingress |
| `CAP_PERFMON` | Accès aux perf events pour les uprobes |
```bash
sudo cp /etc/logcorrelator/config.yml.default /etc/logcorrelator/config.yml
sudo vi /etc/logcorrelator/config.yml
```
Configuration minimale pour activer ClickHouse (`config.yml`) :
```yaml
outputs:
clickhouse:
enabled: true
dsn: clickhouse://data_writer:VotreMotDePasse@localhost:9000/ja4_logs
table: http_logs_raw
batch_size: 500
flush_interval_ms: 200
file:
enabled: true
path: /var/log/logcorrelator/correlated.log
```
Variable d'environnement alternative (dans `/etc/sysconfig/logcorrelator`) :
```bash
LOGCORRELATOR_CLICKHOUSE_DSN=clickhouse://data_writer:VotreMotDePasse@localhost:9000/ja4_logs
```
```bash
sudo systemctl enable --now logcorrelator
sudo systemctl status logcorrelator
```
### Configuration de mod-reqin-log
Le module Apache écrit les requêtes HTTP en JSON vers le socket Unix du correlator.
```bash
# Le RPM installe automatiquement le module dans Apache
# Vérifier le chargement
httpd -M | grep reqin
# La configuration est dans /etc/httpd/conf.d/mod_reqin_log.conf
# Le socket par défaut : /var/run/logcorrelator/http.socket
sudo systemctl restart httpd
```
---
## Étape 6 — Installation des services Python (Docker)
@ -374,7 +333,7 @@ docker compose up -d
```bash
# Services systemd (Go)
sudo systemctl status ja4sentinel logcorrelator httpd
sudo systemctl status ja4ebpf
# Services Docker (Python)
docker compose -f services/bot-detector/docker-compose.yml ps
@ -384,7 +343,7 @@ docker compose -f services/dashboard/docker-compose.yaml ps
### 2. Vérifier l'ingestion des logs
```bash
# Logs bruts ingérés par le correlator
# Logs bruts ingérés par ja4ebpf
clickhouse-client --query "SELECT count() FROM ja4_logs.http_logs_raw"
# Logs parsés par la vue matérialisée
@ -480,27 +439,34 @@ Télécharge et génère tous les fichiers CSV de référence (bot IPs, JA4, ASN
## Schéma réseau récapitulatif
```
┌──────────────┐ Unix socket ┌──────────────┐ ClickHouse ┌──────────────────┐
│ mod-reqin-log│──── http.socket ─────→│ │ INSERT INTO │ │
│ (Apache C11) │ (source A) │ correlator │───→ ja4_logs. │ ClickHouse │
└──────────────┘ │ │ http_logs_raw │ │
│ │ │ ┌──────────────┐ │
┌──────────────┐ Unix socket │ │ MV parse JSON │ │ ja4_logs │ │
sentinel │──── network.socket ──→│ │ ↓ │ │ _raw → _logs│ │
│ (Go+libpcap) │ (source B) └──────────────┘ ja4_logs. │ └──────────────┘ │
└──────────────┘ http_logs │ │
│ │ ┌──────────────┐ │
6 MVs agrégation│ │ ja4_processing│ │
↓ │ │ agg_* (×6) │ │
┌──────────────┐ SELECT features ┌──────────────┐ ja4_processing. │ │ ml_* (×2) │ │
│ bot-detector │←─── view_ai_features ─│ agg_*, view_* │ │ views, dicts│ │
│ (Python 3.11)│ view_thesis_feat ClickHouse │ │ │ audit_logs │ │
│ EIF+AE+XGB │ │ │ ml_all_scores │ └──────────────┘ │
│───→ INSERT scores ────→│ ml_detected_*
└──────────────┘ └──────────────┘ └──────────────────┘
┌──────────────┐ SELECT * │
dashboard │←─── ja4_processing.ml_*, agg_*, views ────────────────────────────┘
│ (FastAPI) │←─── ja4_logs.http_logs ───────────────────────────────────────────┘
└──────────────┘
Trafic HTTPS/HTTP (port 80/443)
|
v
+-----------------+
| ja4ebpf | TC ingress hook -- L3/L4/L5 (SYN, TLS ClientHello)
| (eBPF CO-RE) | uprobe SSL_read -- L7 HTTPS (flux déchiffré)
| | kprobe tcp_recvmsg - L7 HTTP (port 80/8080)
+-----------------+
|
| INSERT batch (HTTP bulk)
v
+-----------------+ +------------------+
| ClickHouse |-- MV parse JSON -> | ja4_logs. |
| | | http_logs_raw |
| | | http_logs |
| |-- 6 MVs agg -----> | ja4_processing. |
| | | agg_*(x6) |
| | | ml_*(x2) |
+-----------------+ +------------------+
^
|
+-----------------+ SELECT view_ai_features
| bot-detector |<-- view_thesis_features
| (EIF+AE+XGB) |
| |--> INSERT ml_all_scores, ml_detected_anomalies
+-----------------+
+-----------------+
| dashboard |<-- ja4_processing.ml_*, agg_*, views
| (FastAPI) |<-- ja4_logs.http_logs
+-----------------+
```

View File

@ -1,6 +1,6 @@
# Guide de développement
Ce guide couvre la compilation, les tests, le packaging et l'extension du monorepo ja4-platform. Toutes les opérations de build et de test s'exécutent dans Docker — aucune toolchain Go, Python ou C n'est requise sur la machine hôte.
Ce guide couvre la compilation, les tests, le packaging et l'extension du monorepo ja4-platform. Toutes les opérations de build et de test s'exécutent dans Docker — aucune toolchain Go, Python, C ou eBPF n'est requise sur la machine hôte.
## Prérequis
@ -11,8 +11,6 @@ Ce guide couvre la compilation, les tests, le packaging et l'extension du monore
| make | 3.81+ | GNU Make |
| git | 2.x | Pour le versioning |
Aucun compilateur Go, Python ou C n'est nécessaire sur la machine hôte.
## Compilation de tous les services
```bash
@ -20,18 +18,14 @@ make build-all
```
Cela construit les images Docker pour :
- `ja4-platform/sentinel:latest`
- `ja4-platform/correlator:latest`
- `ja4-platform/ja4ebpf:latest`
- `ja4-platform/bot-detector:latest`
- `ja4-platform/dashboard:latest`
mod-reqin-log est un module Apache et n'est construit que dans le cadre du processus RPM.
### Compilation individuelle
```bash
make build-sentinel # Binaire Go dans Docker
make build-correlator # Binaire Go dans Docker
make build-ja4ebpf # Bytecode eBPF (clang) + binaire Go dans Docker Rocky Linux
make build-bot-detector # Image Python
make build-dashboard # Image FastAPI + Jinja2
```
@ -46,9 +40,7 @@ make test-all
| Service | Commande | Détails |
|---------|----------|---------|
| sentinel | `make test-sentinel` | Tests Go avec `-race`, nécessite `NET_RAW`/`NET_ADMIN` |
| correlator | `make test-correlator` | Tests Go avec seuil de couverture 80% |
| mod-reqin-log | `make test-mod-reqin-log` | Tests unitaires C (sérialisation JSON, config, headers) |
| ja4ebpf | `make test-ja4ebpf` | Tests Go avec `-race`, nécessite `NET_RAW`/`NET_ADMIN`/`BPF` |
| bot-detector | `make test-bot-detector` | Suite pytest Python |
| dashboard | `make test-dashboard` | pytest pour les routes FastAPI |
| ja4_common (Python) | `make test-ja4common-python` | Tests de la librairie Python partagée |
@ -58,42 +50,32 @@ make test-all
Les tests d'intégration full-stack s'exécutent contre Docker Compose avec une instance ClickHouse réelle :
```bash
make test-integration # 8 phases : build start schema traffic pipeline dashboard bot-detector → sentinel
make test-integration # 8 phases : build -> start -> schema -> traffic -> pipeline -> dashboard -> bot-detector -> ja4ebpf
make test-integration-keep # idem mais laisse la stack en fonctionnement
make test-integration-down # démontage de la stack d'intégration
make test-nginx # stack nginx + ja4ebpf
make test-nginx-varnish # nginx + Varnish + ja4ebpf
make test-hitch-varnish # hitch (TLS) + Varnish + ja4ebpf
make test-all-stacks # les 3 stacks serveur en séquence
```
La suite de tests se trouve dans `tests/integration/` et réinitialise la base de données entre chaque exécution.
La suite de tests se trouve dans `tests/integration/` et réinitialise la base entre chaque exécution.
## Construction des paquets RPM
```bash
make rpm-all
make rpm-ja4ebpf
# RPMs dans services/ja4ebpf/dist/rpm/el{8,9,10}/
```
Construit les RPMs pour sentinel, correlator et mod-reqin-log ciblant Rocky Linux 8/9/10 :
```bash
make rpm-sentinel # → services/sentinel/dist/rpm/
make rpm-correlator # → services/correlator/dist/rpm/
make rpm-mod-reqin-log # → services/mod-reqin-log/dist/rpm/
```
Chaque build RPM utilise un pipeline Docker multi-étapes :
1. L'étape builder compile le binaire (Go) ou l'objet partagé (C)
2. L'étape rpmbuild exécute `rpmbuild` pour chaque distro cible (el8, el9, el10)
3. L'étape de sortie copie les RPMs sur l'hôte via `--output type=local`
### Paquets de distribution
```bash
make dist # Alias for rpm-all
# RPMs in services/<service>/dist/rpm/el{8,9,10}/
```
Le build RPM utilise un pipeline Docker multi-étapes Rocky Linux :
1. L'étape `go-builder` compile le bytecode eBPF (clang/llvm) puis le binaire Go statique
2. Les étapes `rpm-el8`, `rpm-el9`, `rpm-el10` exécutent `rpmbuild` pour chaque distro cible
3. L'étape de sortie `alpine` collecte tous les RPMs via `--output type=local`
## Développement local
### Services Go (sentinel, correlator)
### Service Go/eBPF (ja4ebpf)
Le fichier `go.work` relie les modules Go :
@ -101,23 +83,23 @@ Le fichier `go.work` relie les modules Go :
go 1.24.6
use (
./services/sentinel
./services/correlator
./shared/go/ja4common
./services/ja4ebpf
)
```
Si Go 1.24+ est installé localement, le développement sans Docker est possible :
La compilation locale du bytecode eBPF nécessite `clang`, `llvm` et `bpftool`. En pratique, on passe toujours par Docker :
```bash
# Tests sentinel en local
cd services/sentinel && go test ./... -race -v
# Rebuild du bytecode eBPF + binaire Go (Docker Rocky Linux)
make build-ja4ebpf
# Tests correlator en local
cd services/correlator && go test ./... -race -cover -v
# Tests unitaires ja4ebpf (Docker avec cap NET_RAW + BPF)
make test-ja4ebpf
# Compilation du binaire sentinel (nécessite libpcap-dev)
cd services/sentinel && go build -o ja4sentinel ./cmd/ja4sentinel/
# Test single dans Docker
docker run --rm --cap-add=NET_RAW --cap-add=NET_ADMIN --cap-add=BPF \
ja4ebpf:tests go test -v -run TestDispatcher ./internal/dispatcher/
```
### Services Python (bot-detector, dashboard)
@ -137,12 +119,10 @@ uvicorn backend.main:app --reload --host 0.0.0.0 --port 8000
#### Dépendances Python pour le développement local du bot-detector
Les dépendances principales sont listées dans `services/bot-detector/bot_detector/requirements.txt`. Les librairies ML clés sont :
| Librairie | Usage |
|-----------|-------|
| `isotree` | Extended Isolation Forest (scoreur principal non supervisé) |
| `torch` | Autoencoder (PyTorch, architecture n→64→32→16→32→64→n) |
| `torch` | Autoencoder (PyTorch, architecture n->64->32->16->32->64->n) |
| `xgboost` | Modèle supervisé (entraîné sur les labels SOC) |
| `hdbscan` | Clustering de campagnes d'attaque |
| `shap` | Explicabilité des scores d'anomalie |
@ -167,117 +147,48 @@ services/bot-detector/bot_detector/
└── tests/ # Tests pytest (self-contained, pas d'import lourd)
```
### Module C (mod-reqin-log)
Nécessite `apxs` (outil d'extension Apache) et les headers de développement :
```bash
cd services/mod-reqin-log
make build # Compile mod_reqin_log.so
make test # Exécute les tests unitaires
make rpm # Construit les paquets RPM
```
## Scripts utilitaires
Les scripts dans `scripts/` facilitent l'initialisation et la gestion de l'environnement de développement :
| Script | Commande Make | Description |
|--------|--------------|-------------|
| `init-stack.sh` | `make init-stack` | Initialisation complète de la stack ClickHouse (schéma + données CSV) |
| `import-prod-data.sh` | `make import-prod-data` | Import de données de prod avec décalage temporel |
| `reload-prod-logs.sh` | `make reload-prod-logs` | Export prod réimport dev avec décalage |
| `update-csv-data.sh` | | Téléchargement et génération de toutes les données CSV de référence |
| `reload-prod-logs.sh` | `make reload-prod-logs` | Export prod -> réimport dev avec décalage |
| `update-csv-data.sh` | -- | Téléchargement et génération de toutes les données CSV de référence |
```bash
# Initialisation complète (schéma + données CSV + import prod)
make init-and-import
# Initialisation seule (schéma + CSV)
make init-stack
# Import des données prod pré-exportées
make import-prod-data
# Rechargement depuis la prod
make reload-prod-logs
make init-and-import # Initialisation complète (schéma + CSV + import prod)
make init-stack # Initialisation seule (schéma + CSV)
make import-prod-data # Import des données prod pré-exportées
make reload-prod-logs # Rechargement depuis la prod
```
## Ajout d'un nouveau service
### Service Go
1. Créer le répertoire du service :
1. Créer le répertoire et initialiser le module :
```bash
mkdir -p services/my-service/cmd/my-service
mkdir -p services/my-service/internal
mkdir -p services/my-service/cmd/my-service services/my-service/internal
cd services/my-service && go mod init github.com/antitbone/ja4/my-service
```
2. Initialiser le module Go :
```bash
cd services/my-service
go mod init github.com/antitbone/ja4/my-service
```
3. Ajouter au `go.work` :
2. Ajouter au `go.work` :
```
use (
./services/sentinel
./services/correlator
./services/my-service # ← add this
./shared/go/ja4common
./services/ja4ebpf
./services/my-service # <- add this
)
```
4. Importer la librairie partagée :
```go
import (
"github.com/antitbone/ja4/ja4common/logger"
"github.com/antitbone/ja4/ja4common/config"
"github.com/antitbone/ja4/ja4common/shutdown"
)
```
5. Ajouter les cibles Makefile :
```makefile
build-my-service:
docker build -f services/my-service/Dockerfile -t ja4-platform/my-service:latest .
test-my-service:
docker build -f services/my-service/Dockerfile.dev -t ja4-platform/my-service-tests:latest .
docker run --rm ja4-platform/my-service-tests:latest
```
6. Mettre à jour les dépendances `build-all` et `test-all`.
3. Ajouter les cibles Makefile.
### Service Python
1. Créer le répertoire du service avec un `requirements.txt` ou `pyproject.toml`.
2. Ajouter `ja4-common` comme dépendance (installée depuis `shared/python/ja4_common`).
1. Créer le répertoire avec `requirements.txt`.
2. Ajouter `ja4-common` comme dépendance locale.
3. Utiliser `from ja4_common.clickhouse import get_client` pour l'accès ClickHouse.
4. Ajouter les cibles Makefile en suivant le modèle bot-detector/dashboard.
## Workspace go.work
Le fichier `go.work` à la racine du dépôt relie tous les modules Go, permettant le développement cross-module sans publication :
```
go 1.24.6
use (
./services/sentinel
./services/correlator
./shared/go/ja4common
)
```
Lors de l'ajout d'un nouveau module Go :
1. `go mod init` dans le répertoire du service
2. Ajouter le chemin dans `go.work`
3. Référencer les packages partagés via leur chemin de module : `github.com/antitbone/ja4/ja4common/...`
4. Exécuter `go work sync` pour mettre à jour le workspace
Les deux services Go ont une directive `replace` dans leur `go.mod` pointant vers `../../shared/go/ja4common`. Le workspace prend priorité en développement local ; la directive `replace` est nécessaire pour les builds Docker où `go.work` n'est pas disponible.
## Package Python ja4_common
@ -289,31 +200,10 @@ Le package Python partagé (`shared/python/ja4_common`) fournit :
> **Note :** le dashboard n'utilise PAS ja4_common — il possède son propre client léger `clickhouse-connect` dans `backend/database.py`.
### Extension de ja4_common
1. Ajouter de nouveaux modules sous `shared/python/ja4_common/ja4_common/`
2. Les exporter dans `__init__.py`
3. Ajouter les dépendances dans `pyproject.toml`
4. Lancer les tests : `make test-ja4common-python`
### Utilisation dans un nouveau service
Ajouter dans `requirements.txt` :
```
ja4-common @ file:///app/shared/python/ja4_common
```
Or in Docker, copy the shared library and install:
```dockerfile
COPY shared/python/ja4_common /app/shared/python/ja4_common
RUN pip install /app/shared/python/ja4_common
```
## Variables d'environnement
Chaque service lit sa configuration depuis des variables d'environnement et/ou des fichiers YAML. Consultez la documentation de chaque service pour la référence complète :
Consultez la documentation de chaque service pour la référence complète :
- [Configuration du Sentinel](services/sentinel.md#configuration-reference)
- [Configuration du Correlator](services/correlator.md#configuration-reference)
- [Configuration de ja4ebpf](services/ja4ebpf.md#configuration)
- [Configuration du Bot Detector](services/bot-detector.md#environment-variables)
- [Configuration du Dashboard](services/dashboard.md#configuration)

292
docs/services/ja4ebpf.md Normal file
View File

@ -0,0 +1,292 @@
# ja4ebpf — Agent eBPF CO-RE
## Description
`ja4ebpf` est l'agent de collecte de données de la plateforme ja4-platform. C'est un binaire Go unique qui utilise eBPF CO-RE (Compile Once — Run Everywhere) pour observer passivement le trafic réseau d'un serveur Linux, sans modifier ni interrompre le serveur web cible (Apache, Nginx, Varnish, HAProxy, ou tout processus utilisant OpenSSL/BoringSSL).
Il capture simultanément les métadonnées réseau L3/L4 (TCP SYN), les paramètres TLS L5 (ClientHello), et le contenu applicatif L7 (requêtes HTTP déchiffrées), corr le tout en mémoire par la clé `src_ip:src_port`, et envoie les données vers ClickHouse en batch.
## Architecture interne
```
+-----------------+ TC ingress (XDP/TC) +--------------------+
| réseau entrant |-------------------------->| bpf/tc_capture.c |
| | | |
| SYN packet | --> rb_tcp_syn (16 MB) | Programme eBPF |
| TLS ClientHello| --> rb_tls_hello (16 MB) | CO-RE |
| HTTP port 80 | --> rb_http_plain (32 MB)| |
+-----------------+ +--------------------+
|
+-----------------+ uprobe SSL_read +--------------------+
| serveur web |--------------------------> | bpf/uprobe_ssl.c |
| (OpenSSL) | | |
| flux déchiffré | --> rb_ssl_data (64 MB) | Programme eBPF |
+-----------------+ | CO-RE |
+--------------------+
|
+-----------v-----------+
| Go userspace |
| |
| internal/loader/ |
| - RingBuffer readers |
| - 5 goroutines |
| |
| internal/parser/ |
| - JA4 calculator |
| - H2 preface parser |
| - HTTP/1.1 parser |
| |
| internal/dispatcher/ |
| - Magic Bytes router |
| |
| internal/correlation/ |
| - 256-shard manager |
| - GC 100ms |
| - timeout 500ms |
| |
| internal/writer/ |
| - ClickHouse batch |
+----------+-------------+
|
INSERT batch TCP :9000
|
v
+----------------------+
| ja4_logs. |
| http_logs_raw |
+----------------------+
```
## Hooks eBPF
### TC ingress — Couches L3/L4/L5
Le programme `bpf/tc_capture.c` est attaché à l'interface réseau via **TC (Traffic Control)** en ingress. Il s'exécute pour chaque paquet entrant :
**Paquets TCP SYN** : extraction des options TCP et métadonnées IP depuis les headers du paquet.
- `bpf_skb_load_bytes()` pour lire les options TCP depuis le skb
- Envoyé dans le RingBuffer `rb_tcp_syn` (16 MB)
**ClientHello TLS** : détection du type 0x16 (Handshake) et sous-type 0x01 (ClientHello).
- `bpf_skb_load_bytes()` pour capturer 512 octets du payload
- Envoyé dans le RingBuffer `rb_tls_hello` (16 MB)
**HTTP en clair (port 80/8080)** : pour les connexions non chiffrées.
- SYN/FIN/RST exclus (uniquement les segments porteurs de données)
- Jusqu'à 4096 octets via `bpf_skb_load_bytes()`
- Envoyé dans le RingBuffer `rb_http_plain` (32 MB)
### Uprobe SSL_read — Couche L7
Les **uprobes** s'attachent dynamiquement à `SSL_read` (ou `SSL_read_ex`) dans le processus du serveur web. Elles s'exécutent *après* le déchiffrement TLS, capturant le flux HTTP en clair dans `rb_ssl_data` (64 MB).
La clé de corrélation `src_ip:src_port` est extraite depuis la structure `SSL*` → file descriptor → socket noyau via `bpf_probe_read_kernel()`.
### Kprobe accept4
Un kprobe sur `accept4` peuple `rb_accept` (4 MB) avec le tuple `(src_ip, src_port, fd, pid_tgid)`, permettant d'associer chaque fd SSL à une connexion TCP.
## Corrélation in-memory
Le gestionnaire `internal/correlation` maintient un état par connexion :
| Mécanisme | Valeur |
|-----------|--------|
| Sharding | 256 buckets (hash src_ip:src_port) |
| Timeout orphelin | 500 ms (→ `correlated=0`) |
| Détection Slowloris | 10 s (export partiel) |
| GC interval | 100 ms |
| Keep-Alive | max_keepalives incrémenté par requête |
### Routeur Magic Bytes (dispatcher)
```
Buffer reçu (SSL data ou HTTP plain)
|
+-- starts with "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" --> ProtoHTTP2
|
+-- starts with "GET " / "POST " / "PUT " / ... --> ProtoHTTP1
|
+-- partial H2 preface prefix --> ProtoHTTP2 (buffer)
|
+-- autre --> ProtoUnknown (ignoré)
```
**Fingerprinting HTTP/2 passif** : après détection du preface `PRI * HTTP/2.0...`, le parser itère les frames :
- Frame `SETTINGS` (type `0x04`) : extraction des 7 paramètres, valeur `-1` si absent
- Frame `WINDOW_UPDATE` (type `0x08`, stream 0) : incrément de fenêtre connexion
- Frame `HEADERS` (type `0x01`) : ordre des pseudo-headers (`m,a,s,p`)
## Données collectées
### L3/L4 (TCP SYN)
| Champ | Description |
|-------|-------------|
| `src_ip`, `src_port` | Clé de corrélation |
| `ttl` | Time To Live initial |
| `df_bit` | Don't Fragment bit |
| `ip_id` | IP Identification (0 = Linux/VPN/spoofé) |
| `window_size` | Taille fenêtre TCP SYN |
| `window_scale` | Option Window Scale (RFC 1323) |
| `mss` | Maximum Segment Size |
| `tcp_options` | Options TCP brutes (40 octets max) |
| `tcp_jitter_variance` | Variance jitter inter-SYN |
| `syn_timing_cv` | Délai SYN→ClientHello (ns) |
### L5 (TLS ClientHello)
| Champ | Description |
|-------|-------------|
| `tls_version` | Version TLS |
| `ciphers` | Liste suites cryptographiques |
| `extensions` | Liste extensions TLS |
| `elliptic_curves` | Courbes elliptiques supportées |
| `point_formats` | Formats de points EC |
| `alpn` | ALPN list (h2, http/1.1, ...) |
| `sni` | Server Name Indication |
| `ja4` | Empreinte JA4 calculée Go-side |
| `ja4t` | Empreinte JA4T (TCP) calculée Go-side |
### L7 HTTP/1.1
| Champ | Description |
|-------|-------------|
| `method` | Méthode HTTP |
| `path` | Chemin |
| `query_string` | Paramètres query |
| `http_version` | HTTP/1.0 ou HTTP/1.1 |
| `headers_raw` | En-têtes dans leur ordre d'émission |
| `header_order_signature` | Hash de l'ordre |
| `status_code` | Code de statut |
| `response_size` | Taille réponse (octets) |
| `duration_ms` | Durée requête |
| `timestamp_ns` | Horodatage ns absolu |
### L7 HTTP/2 (preface client)
| Champ | Description |
|-------|-------------|
| `h2_header_table_size` | SETTINGS ID 1 (-1 si absent) |
| `h2_enable_push` | SETTINGS ID 2 |
| `h2_max_concurrent_streams` | SETTINGS ID 3 |
| `h2_initial_window_size` | SETTINGS ID 4 |
| `h2_max_frame_size` | SETTINGS ID 5 |
| `h2_max_header_list_size` | SETTINGS ID 6 |
| `h2_enable_connect_protocol` | SETTINGS ID 8 (RFC 8441) |
| `h2_window_update` | Incrément WINDOW_UPDATE connexion |
| `h2_has_priority` | Flag PRIORITY dans HEADERS frame |
| `h2_pseudo_order` | Ordre pseudo-headers (ex: `m,a,s,p`) |
## eBPF CO-RE
| Aspect | Détail |
|--------|--------|
| Compilateur | `clang` + `llvm` |
| Target | `bpf` (architecture BPF 64 bits) |
| BTF source | `/sys/kernel/btf/vmlinux` (disponible RHEL 8+) |
| Relocations | `cilium/ebpf` résout automatiquement les offsets struct |
| Embed | `go:generate` génère `bpf_bpfel.go` avec bytecode embarqué |
| Compatibilité | Rocky/RHEL Linux 8, 9, 10 (kernel 4.18+) |
## Configuration
```yaml
# /etc/ja4ebpf/config.yml
interface: eth0
target_binary: /usr/sbin/httpd
clickhouse:
dsn: clickhouse://data_writer:pwd@localhost:9000/ja4_logs
table: http_logs_raw
batch_size: 500
flush_interval_ms: 200
correlation:
session_timeout_ms: 500
slowloris_threshold_s: 10
gc_interval_ms: 100
```
### Variables d'environnement
| Variable | Défaut | Description |
|----------|--------|-------------|
| `JA4EBPF_INTERFACE` | `eth0` | Interface réseau |
| `JA4EBPF_TARGET_BINARY` | `/usr/sbin/httpd` | Binaire à hooker (uprobe SSL_read) |
| `JA4EBPF_CLICKHOUSE_DSN` | — | DSN ClickHouse |
| `JA4EBPF_BATCH_SIZE` | `500` | Taille des batchs d'insertion |
| `JA4EBPF_FLUSH_INTERVAL_MS` | `200` | Intervalle de flush (ms) |
| `JA4EBPF_SESSION_TIMEOUT_MS` | `500` | Timeout session orpheline |
| `JA4EBPF_SLOWLORIS_THRESHOLD_S` | `10` | Seuil détection Slowloris (s) |
## Build
```bash
# Build complet (bytecode eBPF + binaire Go) — Docker Rocky Linux
make build-ja4ebpf
# Tests unitaires (nécessite NET_RAW/NET_ADMIN/BPF capabilities)
make test-ja4ebpf
# Build RPMs el8/el9/el10
make rpm-ja4ebpf
# → services/ja4ebpf/dist/rpm/el{8,9,10}/
```
## Structure du code
```
services/ja4ebpf/
├── bpf/
│ ├── bpf_types.h # Structs C partagées + déclarations maps eBPF
│ ├── tc_capture.c # Programme TC ingress (L3/L4/L5 + HTTP plain)
│ └── uprobe_ssl.c # Programme uprobe SSL_read (L7 déchiffré)
├── cmd/ja4ebpf/
│ └── main.go # Point d'entrée : 5 goroutines consumer
├── internal/
│ ├── loader/
│ │ └── loader.go # Chargement eBPF + RingBuffer readers + désérialisation
│ ├── parser/
│ │ ├── ja4.go # Calcul empreintes JA4 / JA4T
│ │ ├── http2.go # Parser HTTP/2 preface (SETTINGS, WINDOW_UPDATE, HEADERS)
│ │ └── http1.go # Parser HTTP/1.1
│ ├── dispatcher/
│ │ └── dispatcher.go # Routeur Magic Bytes (ProtoHTTP1/2/Unknown)
│ ├── correlation/
│ │ └── manager.go # Gestionnaire sessions 256-shard
│ └── writer/
│ └── writer.go # Writer ClickHouse (batch + retry)
├── packaging/
│ ├── rpm/ja4ebpf.spec # Spec RPM (el8/el9/el10)
│ └── systemd/ja4ebpf.service # Unit systemd
├── Dockerfile # Image de production
├── Dockerfile.tests # Image de tests
├── Dockerfile.package # Build RPM multi-distro (5 stages)
└── Makefile
```
## Capabilities Linux requises (SELinux Enforcing)
L'agent tourne sous l'utilisateur `ja4ebpf` (UID/GID 490 fixe). Les capabilities Linux accordées via `AmbientCapabilities` :
| Capability | Raison |
|------------|--------|
| `CAP_BPF` | Chargement des programmes eBPF (kernel 5.8+) |
| `CAP_SYS_ADMIN` | Uprobes + RHEL 8 (kernel 4.18, pré-CAP_BPF) |
| `CAP_NET_ADMIN` | Attachement hooks TC ingress |
| `CAP_PERFMON` | Accès perf events pour les uprobes |
`LimitMEMLOCK=infinity` est requis pour le `mlock()` des maps eBPF.
## Tests d'intégration
Stacks Docker Compose testant l'agent contre différents serveurs web :
```bash
make test-integration # Apache httpd (référence)
make test-nginx # Nginx
make test-nginx-varnish # Nginx + Varnish (reverse proxy)
make test-hitch-varnish # Hitch (TLS) + Varnish
make test-all-stacks # Les 4 stacks en séquence
```