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

View File

@ -534,26 +534,31 @@ func findNginxPIDs() ([]uint32, error) {
return pids, nil
}
// AttachUprobesApache configure les tracepoints/kretprobe read pour capturer
// le trafic HTTP complet depuis Apache httpd. Cette approche utilise les tracepoints
// kernel sys_enter_read et kretprobe __x64_sys_read.
// Le PID Apache est ajouté à la map apache_pid_map pour filtrer les appels read().
func (l *Loader) AttachUprobesApache() error {
// Identique à nginx : sys_enter_recvfrom + kretprobe __x64_sys_recvfrom
kpEnter, err := link.Tracepoint("syscalls", "sys_enter_recvfrom",
l.apacheObjs.TpSysEnterRecvfrom, nil)
// AttachUprobesApache configure les uprobes pour capturer le trafic HTTP
// complet depuis Apache httpd via la fonction apr_socket_recv d'Apache Portable Runtime.
// Cette approche fonctionne sur tous les kernels car elle utilise des uprobes
// au lieu de dépendre de syscalls.
// Le chemin du binaire Apache est lu depuis la configuration.
func (l *Loader) AttachUprobesApache(apacheBinPath string) error {
// Ouvrir l'exécutable Apache pour attacher les uprobes
ex, err := link.OpenExecutable(apacheBinPath)
if err != nil {
return fmt.Errorf("attachement tracepoint sys_enter_recvfrom: %w", err)
return fmt.Errorf("ouverture exécutable Apache %s: %w", apacheBinPath, err)
}
l.uprobeLinks = append(l.uprobeLinks, kpEnter)
kpExit, err := link.Kretprobe("__x64_sys_recvfrom",
l.apacheObjs.KretprobeSysExitRecvfrom, &link.KprobeOptions{})
// Attacher uprobe sur apr_socket_recv (entry)
uprobeEntry, err := ex.Uprobe("apr_socket_recv", l.apacheObjs.UprobeAprSocketRecvEntry, nil)
if err != nil {
return fmt.Errorf("attachement kretprobe __x64_sys_recvfrom: %w", err)
return fmt.Errorf("attachement uprobe apr_socket_recv (entry): %w", err)
}
l.uprobeLinks = append(l.uprobeLinks, kpExit)
l.uprobeLinks = append(l.uprobeLinks, uprobeEntry)
// Attacher uretprobe sur apr_socket_recv (return)
uretprobeExit, err := ex.Uretprobe("apr_socket_recv", l.apacheObjs.UretprobeAprSocketRecv, nil)
if err != nil {
return fmt.Errorf("attachement uretprobe apr_socket_recv (exit): %w", err)
}
l.uprobeLinks = append(l.uprobeLinks, uretprobeExit)
// Trouver les PIDs Apache httpd en cours d'exécution
pids, err := findApachePIDs()
@ -569,7 +574,7 @@ func (l *Loader) AttachUprobesApache() error {
if err := l.AddApachePid(pid); err != nil {
log.Printf("[ja4ebpf] avertissement: ajout PID Apache %d: %v", pid, err)
} else {
log.Printf("[ja4ebpf] tracepoints recvfrom activés pour PID Apache %d", pid)
log.Printf("[ja4ebpf] uprobes apr_socket_recv attachés pour PID Apache %d", pid)
}
}