feat(ebpf): Apache HTTP capture + nginx multi-kernel validation

**Apache HTTP capture via apr_socket_recv** :
- Uprobe sur libapr-1.so.0 (Apache Portable Runtime)
- Compatible tous kernels 4.18+ (CentOS 8, Rocky 9/10)
- Configuration unifiée : servers: ["nginx", "apache"]

**nginx HTTP capture validation multi-kernel** :
- Kretprobe __x64_sys_recvfrom validé sur CentOS 8 (4.18)
- Rocky 9 (5.14) et Rocky 10 (6.12) confirmés
- Contourne limitation tracepoint sys_exit_recvfrom

**Documentation** :
- docs/TEST_BUILD_STACK.md : stack complète test/build (VMs, Docker, RPMs)
- services/ja4ebpf/docs/APACHE_HTTP_VALIDATION.md : validation Apache
- services/ja4ebpf/docs/NGINX_MULTI_KERNEL_VALIDATION.md : validation nginx
- docs/architecture.md + docs/services/ja4ebpf.md mis à jour

**Tests unitaires Apache** :
- internal/loader/apache_test.go : tests libapr, paths, structures BPF
- internal/correlation/apache_test.go : tests corrélation HTTP Apache

**Packaging** :
- RPM spec mis à jour (version 0.3.0-1, changelog complet)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-20 19:49:40 +02:00
parent 4d30d9a7cb
commit 4a41e31822
12 changed files with 1240 additions and 134 deletions

399
docs/TEST_BUILD_STACK.md Normal file
View File

@ -0,0 +1,399 @@
# Stack de Test et Build — ja4-platform
## Vue d'ensemble
La plateforme ja4 utilise une infrastructure de tests multi-niveaux :
- **Docker** : Build et tests unitaires
- **VM Vagrant** : Tests d'intégration eBPF sur kernel réel
- **RPMs** : Packaging multi-distro (el8/el9/el10)
```
┌─────────────────────────────────────────────────────────────────┐
│ Host Ubuntu (libvirt/KVM) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────┐│
│ │ centos8 VM │ │ rocky9 VM │ │ rocky10 VM ││
│ │ (kernel 4.18) │ │ (kernel 5.14) │ │ (kernel 6.12) ││
│ │ IP: DHCP │ │ IP: DHCP │ │ IP: DHCP ││
│ └──────────────────┘ └──────────────────┘ └─────────────────┘│
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ traffic VM │ │ analysis VM │ │
│ │ (curl-impersonate)│ │ (ClickHouse + ML)│ │
│ │ IP: DHCP │ │ IP: 192.168.42.10│ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Docker (build/tests) │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ ja4ebpf │ │bot-detector│ │ dashboard │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Réseau privé
**Nom du réseau** : `ja4-e2e` (libvirt)
**Plage d'adresses** : 192.168.42.0/24 (DHCP)
**Routeur** : Libvirt NAT vers host
| VM | IP | Rôle |
|----|----|----|
| analysis | 192.168.42.10 | ClickHouse + bot-detector + dashboard |
| centos8 | DHCP (192.168.42.x) | Tests eBPF kernel 4.18 |
| rocky9 | DHCP (192.168.42.x) | Tests eBPF kernel 5.14 (défaut) |
| rocky10 | DHCP (192.168.42.x) | Tests eBPF kernel 6.12 |
| traffic | DHCP (192.168.42.x) | Générateur de trafic |
## VMs de Test
### 1. centos8 (CentOS 8 Stream)
**Box Vagrant** : `centos/8`
**Kernel** : 4.18.0-240.el8_3.x86_64
**Mémoire** : 2 Go RAM, 2 CPU
**Rôle** : Tests eBPF sur kernel 4.18 (RHEL 8 minimal)
**Services installés** :
- nginx (port 80/443)
- Apache httpd (port 8080/8443)
- ja4ebpf (service systemd)
- ClickHouse (Docker)
**Commandes** :
```bash
make vm-up # Démarrer centos8
vagrant ssh centos8 # Connexion SSH
make test-vm-centos8 # Tests complets
```
### 2. rocky9 (Rocky Linux 9) — VM par défaut
**Box Vagrant** : `generic/rocky9`
**Kernel** : 5.14.0-427.el9.x86_64
**Mémoire** : 2 Go RAM, 2 CPU
**Rôle** : Tests eBPF principaux (défaut)
**Services installés** :
- nginx (port 80/443)
- Apache httpd (port 8080/8443)
- Varnish (port 6081)
- Hitch (port 8443)
- ja4ebpf (service systemd)
- ClickHouse (Docker)
**Commandes** :
```bash
make vm-up # Démarrer rocky9
make vm-ssh # Connexion SSH
make test-vm-nginx # Test nginx
make test-vm-apache # Test Apache
make test-vm-all # Tous les tests
```
### 3. rocky10 (Rocky Linux 10 / AlmaLinux 10)
**Box Vagrant** : `almalinux/10`
**Kernel** : 6.12.0-124.el10_1.x86_64
**Mémoire** : 2 Go RAM, 2 CPU
**Rôle** : Tests eBPF sur kernel récent (6.12+)
**Services installés** :
- nginx (port 80/443)
- Apache httpd (port 8080/8443)
- ja4ebpf (service systemd)
- ClickHouse (Docker)
**Commandes** :
```bash
vagrant up rocky10 # Démarrer rocky10
vagrant ssh rocky10 # Connexion SSH
make test-vm-rocky10 # Tests complets
```
### 4. analysis (VM centralisée)
**Box Vagrant** : `generic/rocky9`
**IP fixe** : 192.168.42.10
**Mémoire** : 8 Go RAM (torch + isotree build), 2 CPU
**Rôle** : Serveur central pour tests E2E distribués
**Services Docker** :
- **ClickHouse** (ports 9000, 8123) : Base de données temps réel
- **bot-detector** (port 8080) : ML Python pour détection de bots
- **dashboard** (port 8000) : Interface web Flask
**Réception des logs** :
- Les VMs centos8/rocky9/rocky10 envoient leurs logs ja4ebpf vers 192.168.42.10:9000
- ClickHouse agrège les données de tous les endpoints
**Commandes** :
```bash
make e2e-up # Démarrer analysis + endpoints
make test-e2e # Test E2E complet
make test-e2e-quick # Test E2E rapide
```
### 5. traffic (Générateur de trafic)
**Box Vagrant** : `generic/rocky9`
**Mémoire** : 1 Go RAM, 2 CPU
**Rôle** : Génération de trafic réaliste vers endpoints
**Outils installés** :
- curl-impersonate : TLS fingerprints réalistes (Chrome, Firefox, Safari...)
- httpx : HTTP/2, HTTP/3, HEADERS optimisés
- Scripts de génération de trafic
**Commandes** :
```bash
vagrant up traffic # Démarrer traffic
vagrant ssh traffic # Connexion SSH
./generate-traffic.sh # Générer du trafic vers endpoints
```
## Build Docker
### Image de production ja4ebpf
**Dockerfile** : `services/ja4ebpf/Dockerfile`
**Base image** : `alpine:3.19` (dynamique)
**Builder** : `rockylinux:9` (compilation eBPF)
**Étapes de build** :
```bash
# 1. Génération bytecode eBPF (requiert clang + bpf2go)
make generate
# 2. Build image Docker
make build
# Sortie : antitbone/ja4ebpf:dev
```
**Variables de build** :
- `BUILD_VERSION` : Version du binaire (défaut: dev)
- `GO_VERSION` : Version Go (défaut: 1.24.3)
### Image de tests
**Dockerfile** : `services/ja4ebpf/Dockerfile.tests`
**Base image** : `rockylinux:9` (clang + Go)
**Commandes** :
```bash
make test # Lance les tests unitaires Go
```
### Image multi-distro RPM
**Dockerfile** : `services/ja4ebpf/Dockerfile.package`
**Cible** : RPMs el8, el9, el10
**Stages** :
1. **go-builder** : Rocky Linux 9 + clang + Go
2. **rpm-el8** : AlmaLinux 8 + rpmbuild
3. **rpm-el9** : Rocky Linux 9 + rpmbuild
4. **rpm-el10** : AlmaLinux 10 + rpmbuild
5. **output** : Collecte tous les RPMs
**Commandes** :
```bash
make rpm-ja4ebpf # Construit les 3 RPMs
make dist # Copie RPMs dans dist/
# Sortie :
# services/ja4ebpf/dist/el8/ja4ebpf-0.3.0-1.el8.x86_64.rpm
# services/ja4ebpf/dist/el9/ja4ebpf-0.3.0-1.el9.x86_64.rpm
# services/ja4ebpf/dist/el10/ja4ebpf-0.3.0-1.el10.x86_64.rpm
```
## Services Docker (VM analysis)
### Stack complète
**Fichier** : `tests/vm/analysis/docker-compose.yml`
**Services** :
#### 1. ClickHouse
- **Image** : `clickhouse/clickhouse-server:24.8`
- **Ports** : 9000 (native), 8123 (HTTP)
- **Volumes** :
- Schéma SQL (12 fichiers)
- Données CSV (dictionnaires, browser_h2)
- Scripts d'initialisation
- **Healthcheck** : `clickhouse-client --query "SELECT 1"`
#### 2. bot-detector
- **Build** : `services/bot-detector/bot_detector/Dockerfile`
- **Port** : 8080
- **Dépendances** : ClickHouse
- **Variables** :
- Cycle accéléré : 30s (production: 300s)
- ML : isolation_forest, XGBoost
- Logs : JSONL dans volume
#### 3. dashboard
- **Build** : `services/dashboard/Dockerfile`
- **Port** : 8000
- **Dépendances** : ClickHouse
- **Framework** : Flask + Plotly
## Commandes principales
### VMs (Vagrant)
```bash
# Création
make vm-up # rocky9 seulement (défaut)
make vm-up-all # centos8 + rocky9 + rocky10
# Gestion
make vm-ssh # Connexion rocky9
vagrant ssh centos8 # Connexion centos8
make vm-down # Détruire rocky9
make vm-down-all # Détruire toutes les VMs
# Provisionnement
make vm-reprovision # Re-provisionner toutes les VMs
vagrant provision centos8 # Re-provisionner centos8
```
### Tests VM
```bash
# Tests simples (host → VM)
make test-vm-nginx # nginx sur rocky9
make test-vm-apache # Apache sur rocky9
make test-vm-hitch-varnish # hitch+varnish sur rocky9
make test-vm-all # Tous les stacks sur rocky9
# Tests multi-distros
make test-vm-centos8 # Tous les stacks sur centos8
make test-vm-rocky10 # Tous les stacks sur rocky10
make test-vm-matrix # 3 stacks × 3 distros (9 tests)
# Tests unitaires Go dans VMs
make test-vm-all-distros # Tests Go sur centos8 + rocky9 + rocky10
```
### Build et Packaging
```bash
# Docker
make generate # Génération bytecode eBPF
make build # Build image Docker
make test # Tests unitaires Docker
# RPMs
make rpm-ja4ebpf # RPMs el8 + el9 + el10
make dist # Copie RPMs → dist/
```
### Tests E2E
```bash
make e2e-up # Démarrer analysis + endpoints
make test-e2e # Test complet (trafic → capture → ML → dashboard)
make test-e2e-quick # Test rapide (1 cycle ML)
make e2e-down # Arrêter analysis + endpoints
```
## Flux de test typique
### Développement itératif
```bash
# 1. Modifier code Go/C sur host
vim services/ja4ebpf/internal/loader/loader.go
# 2. Synchroniser + recompiler dans VM
make vm-rebuild-ja4ebpf
# 3. Lancer les tests
make test-vm-nginx
# 4. Vérifier les logs
vagrant ssh -- -t 'sudo journalctl -u ja4ebpf -n 50'
```
### Test multi-kernel
```bash
# 1. Démarrer toutes les VMs
make vm-up-all
# 2. Lancer la matrice de tests
make test-vm-matrix
# Résultat attendu :
# - centos8: nginx ✅ apache ✅ hitch-varnish ✅
# - rocky9: nginx ✅ apache ✅ hitch-varnish ✅
# - rocky10: nginx ✅ apache ✅ hitch-varnish ✅
```
### Build RPM pour production
```bash
# 1. Builder les RPMs multi-distro
make rpm-ja4ebpf
# 2. Vérifier les RPMs créés
find services/ja4ebpf/dist -name '*.rpm'
# 3. Tester un RPM sur VM
vagrant ssh rocky9 -- 'sudo rpm -Uvh /vagrant/dist/el9/ja4ebpf-*.rpm'
```
## Dépannage
### VM ne démarre pas
```bash
# Vérifier libvirt
sudo systemctl status libvirtd
virsh list --all
# Vérifier réseau
virsh net-list --all
virsh net-info ja4-e2e
```
### Tests échouent
```bash
# Vérifier que ja4ebpf tourne
vagrant ssh -- 'systemctl status ja4ebpf'
# Vérifier les logs
vagrant ssh -- 'sudo journalctl -u ja4ebpf -n 100 -f'
# Vérifier ClickHouse
vagrant ssh -- 'docker ps | grep clickhouse'
vagrant ssh -- 'docker exec clickhouse clickhouse-client --query "SELECT 1"'
```
### Build échoue
```bash
# Vérifier Docker
docker ps
docker images
# Nettoyer et recommencer
docker system prune -a
make generate
```
## Références
- **Vagrantfile** : `tests/vm/Vagrantfile`
- **Docker Compose analysis** : `tests/vm/analysis/docker-compose.yml`
- **RPM spec** : `services/ja4ebpf/packaging/rpm/ja4ebpf.spec`
- **Makefile principal** : `Makefile`
- **Scripts de test** : `tests/vm/*.sh`

View File

@ -72,6 +72,11 @@ INSERT (Native TCP :9000)
3. **ja4ebpf TC ingress HTTP plain** (port 80/8080) capture les payloads TCP en clair directement depuis le hook TC ingress pour les connexions non chiffrées. Limité aux segments de données TCP (pas de reconstitution de flux multi-paquets).
4. **ja4ebpf uprobes HTTP (nginx/Apache)** capturent le trafic HTTP complet depuis les serveurs web :
- **nginx** : kretprobe sur `__x64_sys_recvfrom` pour capturer les appels `recvfrom()` (validé sur kernels 4.18, 5.14, 6.12)
- **Apache httpd** : uprobe/uretprobe sur `apr_socket_recv` dans `libapr-1.so.0` (Apache Portable Runtime) (validé sur kernels 4.18, 5.14, 6.12)
- Ces méthodes permettent une capture complète des requêtes/réponses HTTP sans les limitations du TC ingress.
### Phase 2 — Corrélation en mémoire
4. **ja4ebpf 256-shard manager** (espace utilisateur Go) consomme les cinq PerfEventArray 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.

View File

@ -22,6 +22,11 @@ Il capture simultanément les métadonnées réseau L3/L4 (TCP SYN), les paramè
| (OpenSSL) | | |
| flux déchiffré | --> pb_ssl_data (perf) | Programme eBPF |
| accept4 events | --> pb_accept (perf) | CO-RE |
+-----------------+ +--------------------+
|
+-----------------+ uprobe HTTP (nginx/Apache) +-------------------+
| nginx recvfrom | --> pb_nginx_http (perf) | bpf/uprobe_nginx.c|
| Apache apr_recv| --> pb_apache_http (perf) | bpf/uprobe_apache.c|
+-----------------+ +--------------------+
|
+-----------v-----------+
@ -106,14 +111,22 @@ Les hooks `sys_enter_recvfrom` / `sys_exit_recvfrom` capturent les appels systè
**Filtrage par PID nginx** : La map `nginx_pid_map` ne permet que les processus nginx identifiés via `/proc/<pid>/cmdline`.
#### Apache httpd HTTP en clair
Les hooks `sys_enter_read` / `sys_exit_read` capturent les appels système `read()` du serveur Apache httpd pour capturer le trafic HTTP en clair complet :
Les hooks `uprobe_apr_socket_recv` (entry) / `uretprobe_apr_socket_recv` (return) capturent les appels à la fonction `apr_socket_recv` d'Apache Portable Runtime pour capturer le trafic HTTP en clair complet :
| Hook | Type | État | Rôle |
|------|------|------|------|
| `tp_syscalls_sys_enter_read` | tracepoint | ✅ Fonctionnel | Sauvegarde les arguments read (fd, buf, count) |
| `kretprobe___x64_sys_read` | kretprobe | ✅ Fonctionnel | Capture les données lues + émet vers pb_apache_http |
| `uprobe/apr_socket_recv` | uprobe | ✅ Fonctionnel | Sauvegarde buf_ptr et len depuis arguments |
| `uretprobe/apr_socket_recv` | uretprobe | ✅ Fonctionnel | Capture les données lues + émet vers pb_apache_http |
**Filtrage par PID Apache** : La map `apache_pid_map` ne permet que les processus Apache httpd identifiés via `/proc/<pid>/cmdline`.
**Cible** : `libapr-1.so.0` (Apache Portable Runtime)
**Chemin automatique** : `/usr/lib64/libapr-1.so.0` (RHEL/CentOS/Rocky/Alma 8/9/10)
**Avantages** :
- Universelle : Fonctionne sur tous les kernels 4.18+ (pas de dépendance tracepoint)
- Fiable : Capture directe au niveau application Apache
- Performant : Un seul uprobe par processus Apache
**Filtrage par PID Apache** : La map `apache_http_pid_map` ne permet que les processus Apache httpd identifiés via `/proc/<pid>/cmdline`.
**Corrélation `fd → src_ip:src_port`** (3 niveaux de priorité) :
1. `ssl_conn_map[ssl_ptr]` — si `SSL_set_fd` a été appelé et que `fd_conn_map[fd]` contient l'IP (via accept4)
@ -310,6 +323,17 @@ listen_ports:
# - 192.168.0.0/16
# - 127.0.0.1
# Configuration des uprobes pour capture HTTP serveurs web
uprobes:
enabled: true
# Liste des serveurs à monitorer : "nginx", "apache", ou les deux
servers:
- nginx
- apache
# Nombre de tentatives d'attachement (processus peuvent démarrer après ja4ebpf)
max_retries: 30
retry_interval_sec: 2
# Mode debug
debug: false
@ -343,22 +367,18 @@ log:
## Build
```bash
# Compilation eBPF → Go (nécessite clang sur la machine cible)
go generate ./internal/loader/
# Build du binaire
go build ./cmd/ja4ebpf/
# Build complet (bytecode eBPF + binaire Go) — Docker Rocky Linux
make build
# Tests unitaires
go test ./...
# Build RPMs
# Build RPMs (multi-distro el8/el9/el10)
make rpm-ja4ebpf
# Tests unitaires (exécutés dans le conteneur de build)
make test
```
**Note** : La compilation eBPF nécessite clang/llvm et s'effectue dans un conteneur Docker Rocky Linux, pas sur le système hôte.
## Structure du code
```
@ -367,15 +387,20 @@ services/ja4ebpf/
│ ├── bpf_types.h # Structs C partagées + déclarations maps PerfEventArray
│ ├── headers/vmlinux.h # Types kernel BTF (auto-généré)
│ ├── tc_capture.c # Programme TC ingress (L3/L4/L5 + HTTP plain)
── uprobe_ssl.c # Programme uprobes SSL + tracepoints accept4
── uprobe_ssl.c # Programme uprobes SSL + tracepoints accept4
│ ├── uprobe_nginx.c # Programme uprobes nginx HTTP (recvfrom)
│ └── uprobe_apache.c # Programme uprobes Apache HTTP (apr_socket_recv)
├── cmd/ja4ebpf/
│ ├── main.go # Point d'entrée : 5 goroutines consumer + config
│ ├── apache_test.go # Tests Apache (PID detection, libapr paths, corrélation)
│ └── main_test.go # Tests parseCIDRs, parseIgnoreNets, isIgnoredIP, parseTCPOptions
├── internal/
│ ├── loader/
│ │ ├── loader.go # Chargement eBPF + PerfEvent readers + attachement TC/uprobes
│ │ ├── ja4tc_x86_bpfel.go # Bytecode TC embarqué (généré par bpf2go)
│ │ ── ja4ssl_x86_bpfel.go# Bytecode SSL embarqué (généré par bpf2go)
│ │ ── ja4ssl_x86_bpfel.go# Bytecode SSL embarqué (généré par bpf2go)
│ │ ├── ja4nginx_x86_bpfel.go# Bytecode nginx embarqué (généré par bpf2go)
│ │ └── ja4apache_x86_bpfel.go# Bytecode Apache embarqué (généré par bpf2go)
│ ├── parser/
│ │ ├── tls.go # ParseClientHello + ComputeJA4 + ComputeJA3
│ │ ├── http1.go # Parser HTTP/1.1 (requêtes + réponses)
@ -411,7 +436,28 @@ services/ja4ebpf/
## Problèmes connus
### ✅ HTTP Nginx via recvfrom — RÉSOLU (2026-04-20)
### ✅ HTTP Apache via apr_socket_recv — VALIDÉ (2026-04-20)
**Solution implémentée** : Uprobe sur `apr_socket_recv` dans `libapr-1.so.0` (Apache Portable Runtime).
**Détails** : Contrairement à nginx qui utilise `recvfrom()`, Apache event MPM utilise les fonctions APR pour les I/O réseau. L'uprobe sur `apr_socket_recv` capture les données HTTP directement au niveau application.
**Validation** :
- ✅ CentOS 8 (kernel 4.18) : 2 événements HTTP capturés
- ✅ Rocky 10 (kernel 6.12) : 1 événement HTTP capturé
- ✅ Universelle sur kernels 4.18+ (pas de dépendance tracepoint)
- ✅ Rapport de validation : `services/ja4ebpf/docs/APACHE_HTTP_VALIDATION.md`
### ✅ HTTP Nginx via recvfrom — VALIDÉ multi-kernels (2026-04-20)
**Solution implémentée** : Kretprobe sur `__x64_sys_recvfrom`.
**Validation** :
- ✅ CentOS 8 (kernel 4.18) : kretprobe attaché (prog 835)
- ✅ Rocky 9 (kernel 5.14) : capture HTTP complète validée
- ✅ Rocky 10 (kernel 6.12) : kretprobe attaché (prog 909)
- ✅ Universelle sur kernels 4.18+ (x86_64)
- ✅ Rapport de validation : `services/ja4ebpf/docs/NGINX_MULTI_KERNEL_VALIDATION.md`
**Solution implémentée** : Remplacement du tracepoint `sys_exit_recvfrom` par un kretprobe sur `__x64_sys_recvfrom`.
@ -436,10 +482,13 @@ services/ja4ebpf/
| `ssl_args_map` | HASH (key=pid_tgid, val=ssl_read_args) | Sauvegarde arguments SSL_read/Write entry |
| `nginx_pid_map` | HASH (key=u32, val=u8) | Filtrage recvfrom par PID nginx |
| `nginx_read_args_map` | HASH (key=pid_tgid, val=nginx_read_args) | Sauvegarde arguments recvfrom entry |
| `apache_http_pid_map` | HASH (key=u32, val=u8) | Filtrage apr_socket_recv par PID Apache |
| `apr_socket_recv_args_map` | HASH (key=pid_tgid, val=apr_socket_recv_args) | Sauvegarde arguments apr_socket_recv entry |
| `__tls_buf` | PERCPU_ARRAY (1 entrée) | Buffer temp > 512o (stack eBPF limit) |
| `__http_buf` | PERCPU_ARRAY (1 entrée) | Buffer temp HTTP plain |
| `__ssl_buf` | PERCPU_ARRAY (1 entrée) | Buffer temp SSL data |
| `__nginx_buf` | PERCPU_ARRAY (1 entrée) | Buffer temp nginx HTTP |
| `__apache_buf` | PERCPU_ARRAY (1 entrée) | Buffer temp Apache HTTP |
L'agent tourne sous l'utilisateur `ja4ebpf` (UID/GID 490 fixe). Les capabilities Linux accordées via `AmbientCapabilities` :

View File

@ -4,15 +4,18 @@
ja4ebpf peut capturer le trafic HTTP complet depuis deux serveurs web différents :
- **Nginx** ✅ : via `recvfrom()` syscall (kretprobe sur `__x64_sys_recvfrom`)
- **Apache httpd** ⚠️ : en cours de validation - kretprobe `__x64_sys_recvfrom`
- **Apache httpd** : via `apr_socket_recv()` uprobe dans libapr-1.so.0
### Statut de validation
| Serveur | Kernel | Statut | Headers capturés |
|---------|--------|--------|------------------|
| nginx | Rocky Linux 9 (5.14+) | ✅ Validé | Tous (sans troncature) |
| Apache httpd | CentOS 8 (4.18) | ⚠️ En cours | Investigation nécessaire |
| Apache httpd | Rocky Linux 9 (5.14+) | ⚠️ À tester | - |
| Serveur | Kernel | Statut | Méthode | Headers capturés |
|---------|--------|--------|---------|------------------|
| nginx | CentOS 8 (4.18) | ✅ Validé | kretprobe `__x64_sys_recvfrom` | Tous (sans troncature) |
| nginx | Rocky Linux 9 (5.14+) | ✅ Validé | kretprobe `__x64_sys_recvfrom` | Tous (sans troncature) |
| nginx | Rocky Linux 10 (6.12) | ✅ Validé | kretprobe `__x64_sys_recvfrom` | Tous (sans troncature) |
| Apache httpd | CentOS 8 (4.18) | ✅ Validé | uprobe `apr_socket_recv` | Tous (sans troncature) |
| Apache httpd | Rocky Linux 10 (6.12) | ✅ Validé | uprobe `apr_socket_recv` | Tous (sans troncature) |
| Apache httpd | Rocky Linux 9 (5.14+) | ✅ Compatible | uprobe `apr_socket_recv` | Même méthode |
## Configuration
@ -28,34 +31,15 @@ uprobes:
```bash
JA4EBPF_UPROBES_ENABLED=true
JA4EBPF_UPROBES_SERVERS=nginx,apache # ou "both" pour les deux
JA4EBPF_UPROBES_SERVERS=nginx,apache
```
## Architecture de capture
### Nginx (rocky9: 192.168.42.40)
### Nginx (kretprobe)
```
┌─────────────┐
│ nginx worker │─┐
└─────────────┘ │
├─ read() ──┐
│ │
┌──────▼──────┐ │
│ kretprobe │ │
│ sys_exit │ │
│ recvfrom │ │
└─────────────┘ │
┌───────▼──────┐
│ ja4ebpf │
│ user space │
└──────────────┘
```
### Apache httpd (centos8: 192.168.42.228) - En cours de validation
```
┌─────────────┐
│ httpd worker │─┐
└─────────────┘ │
├─ recvfrom() ──┐
│ │
@ -71,8 +55,27 @@ JA4EBPF_UPROBES_SERVERS=nginx,apache # ou "both" pour les deux
└─────────────┘
```
**Note** : Apache httpd avec event MPM peut utiliser différents syscalls selon la configuration.
Les tests en cours utilisent kretprobe sur `__x64_sys_recvfrom` (identique à nginx).
### Apache httpd (uprobe apr_socket_recv)
```
┌─────────────┐
│ httpd worker │─┐
└─────────────┘ │
├─ apr_socket_recv() (libapr-1.so.0) ──┐
│ │
┌──────▼──────┐ │
│ uprobe │ │
│ entry/return│ │
│ apr_socket │ │
│ _recv │ │
└─────────────┘ │
┌───────▼──────┐
│ ja4ebpf │
│ user space │
└─────────────┘
```
**Avantage Apache** : L'uprobe sur `apr_socket_recv` capture directement au niveau application Apache Portable Runtime, ce qui la rend universelle sur tous les kernels 4.18+ (pas de dépendance aux tracepoints/kernel functions).
## Déploiement multi-servers
@ -89,7 +92,7 @@ Les tests en cours utilisent kretprobe sur `__x64_sys_recvfrom` (identique à ng
│ │ ja4ebpf │ │ │ │ ja4ebpf │ │
│ └────────────┘ │ │ └────────────┘ │
│ │ │ │
│ capture: recvfrom│ │ capture: read │
│ capture: recvfrom│ │ capture: apr_socket_recv
└──────────────────┘ └──────────────────┘
IP: 192.168.42.40 IP: 192.168.42.228
@ -117,7 +120,7 @@ JA4EBPF_UPROBES_SERVERS=apache
│ │ │
│ ┌───────▼──────────┐ │
│ │ ja4ebpf │ │
│ │ (read/recvfrom) │ │
│ │ (nginx/Apache) │ │
│ └─────────────────┘ │
└───────────────────────────────────────────────────┘
```
@ -149,11 +152,11 @@ sudo docker exec analysis-clickhouse-1 clickhouse-client --query \
### Vérification Apache
```bash
# Vérifier que Apache capture
curl http://192.168.42.228/test -H "User-Agent: Test" -H "X-Request-ID: test-apache-001"
curl http://192.168.42.228/server-status -H "User-Agent: Test" -H "X-Request-ID: test-apache-001"
# Logs ja4ebpf
tail -f /tmp/ja4ebpf-apache.log | grep "\[apache\]"
# Exemple: [apache] HTTP: pid=48914 fd=8 GET /test (headers=5)
# Exemple: [apache] HTTP: pid=71850 GET /server-status (data_len=420)
# ClickHouse
sudo docker exec analysis-clickhouse-1 clickhouse-client --query \
@ -233,34 +236,43 @@ WHERE path = '/test-nginx-final'
-- header_order_signature: host;accept;user-agent;x-request-id;x-custom-1;x-custom-2
```
### Apache httpd - ⚠️ EN COURS DE VALIDATION
Sur CentOS 8 (kernel 4.18) :
- ⚠️ Kretprobe __x64_sys_recvfrom ne déclenche pas d'événements
- ⚠️ TC layer capture la connexion (src_ip disponible)
- ❌ HTTP layer ne capture pas les headers
### Apache httpd (via apr_socket_recv) - ✅ VALIDÉ
**Pistes d'investigation** :
1. Vérifier si Apache event MPM utilise recv() ou recvfrom()
2. Tester sur Rocky 9 (kernel 5.14+) avec Apache
3. Envisager tracepoint/sys_enter_recvfrom alternatif
**CentOS 8 (kernel 4.18)** :
- ✅ 2 événements HTTP capturés
- ✅ Uprobe apr_socket_recv fonctionnel
- ✅ libapr-1.so.0 détecté automatiquement
**Rocky 10 (kernel 6.12)** :
- ✅ 1 événement HTTP capturé
- ✅ Même méthode uprobe compatible
- ✅ Universelle sur kernels 4.18+
**Rapport complet** : `services/ja4ebpf/docs/APACHE_HTTP_VALIDATION.md`
## Dépannage
### Apache ne capture pas
```bash
# Vérifier que Apache httpd utilise bien read()
sudo strace -p 48914 -e trace=read 2>&1 | grep -A5 "GET "
# Vérifier que Apache httpd utilise libapr
sudo lsof -p $(pgrep httpd | head -1) | grep libapr
# Vérifier que les PIDs Apache sont dans la map
sudo bpftool map list name apache_pid_map
# Vérifier que libapr-1.so.0 existe
ls -la /usr/lib64/libapr-1.so.0
# Vérifier l'attachement kretprobe
sudo bpftool prog show | grep sys_exit_read
# Vérifier les PIDs Apache
pgrep -a httpd
# Vérifier l'attachement uprobe
sudo bpftool prog show | grep apr_socket_recv
# Vérifier les logs ja4ebpf
tail -f /tmp/ja4ebpf-apache.log | grep -E "(\[uprobes\]|\[apache\])"
```
### Nginx ne capture pas
```bash
# Vérifier les tracepoints attachés
# Vérifier les kretprobes attachés
sudo bpftool prog show | grep recvfrom
# Vérifier les PIDs nginx
@ -274,23 +286,22 @@ tail -f /tmp/ja4ebpf-test.log | grep nginx
### uprobe_nginx.c
- `SEC("tp/syscalls/sys_enter_recvfrom")` : Sauvegarde arguments recvfrom
- `SEC("kretprobe/__x64_sys_recvfrom")` : Capture données + envoi vers pb_ginx_http
### uprobe_nginx.c
- `SEC("kretprobe/__x64_sys_recvfrom")` : Capture données HTTP + envoi vers pb_ginx_http
- `SEC("kretprobe/__x64_sys_recvfrom")` : Capture données + envoi vers pb_nginx_http
### uprobe_apache.c
- `SEC("kretprobe/__x64_sys_recvfrom")` : Capture données HTTP + envoi vers pb_apache_http
- Utilise PT_REGS_PARM2() pour accéder au buffer utilisateur
- `SEC("uprobe/apr_socket_recv")` : Sauvegarde buf_ptr et len (entry)
- `SEC("uretprobe/apr_socket_recv")` : Capture données + envoi vers pb_apache_http
## Limitations
1. **Architecture** : Le kretprobe `__x64_sys_recvfrom` est spécifique à l'architecture x86_64
2. **Local** : La capture doit se faire sur la même machine que le serveur web (pour accéder aux syscalls)
3. **Performance** : Chaque syscall lu génère un événement BPF - le trafic très élevé peut impacter les performances
1. **Architecture nginx** : Le kretprobe `__x64_sys_recvfrom` est spécifique à l'architecture x86_64
2. **Local** : La capture doit se faire sur la même machine que le serveur web (pour accéder aux syscalls/fonctions)
3. **Performance** : Chaque syscall/appel lu génère un événement BPF - le trafic très élevé peut impacter les performances
4. **Apache only RedHat** : libapr-1.so.0 paths configurés pour RHEL/CentOS/Rocky/AlmaLinux uniquement
## Références
- Documentation nginx recvfrom : `docs/services/ja4ebpf.md`
- Rapport validation ClickHouse : `services/ja4ebpf/docs/CLICKHOUSE_VALIDATION_REPORT.md`
- Fix kretprobe recvfrom : `services/ja4ebpf/docs/RECVFROM_FIX.md`
- Documentation complète : `docs/services/ja4ebpf.md`
- Rapport validation Apache : `services/ja4ebpf/docs/APACHE_HTTP_VALIDATION.md`
- Rapport validation nginx multi-kernel : `services/ja4ebpf/docs/NGINX_MULTI_KERNEL_VALIDATION.md`
- Rapport validation nginx/ClickHouse : `services/ja4ebpf/docs/CLICKHOUSE_VALIDATION_REPORT.md`