diff --git a/docs/services/ja4ebpf.md b/docs/services/ja4ebpf.md index ec6d332..686dffb 100644 --- a/docs/services/ja4ebpf.md +++ b/docs/services/ja4ebpf.md @@ -93,8 +93,9 @@ Les uprobes s'attachent dynamiquement aux fonctions OpenSSL dans `libssl.so` : | `SSL_read` | uprobe + uretprobe | Capture les requêtes du client (direction=0) | | `SSL_write` | uprobe + uretprobe | Capture les réponses du serveur (direction=1) | -### Tracepoints/Kretprobe recvfrom (Nginx HTTP en clair) +### Tracepoints/Kretprobe HTTP serveurs web (Nginx, Apache) +#### Nginx HTTP en clair Les hooks `sys_enter_recvfrom` / `sys_exit_recvfrom` capturent les appels système `recvfrom()` du serveur Nginx pour capturer le trafic HTTP en clair complet : | Hook | Type | État | Rôle | @@ -102,10 +103,18 @@ Les hooks `sys_enter_recvfrom` / `sys_exit_recvfrom` capturent les appels systè | `tp_syscalls_sys_enter_recvfrom` | tracepoint | ✅ Fonctionnel | Sauvegarde les arguments recvfrom (sockfd, buf_ptr, len) | | `tp_sys_exit_recvfrom` | kretprobe | ✅ Fonctionnel | Capture les données lues + émet vers pb_ginx_http | -**Note** : Le kretprobe sur `__x64_sys_recvfrom` remplace le tracepoint `sys_exit_recvfrom` qui échouait avec "permission denied" sur Rocky Linux 9+. - **Filtrage par PID nginx** : La map `nginx_pid_map` ne permet que les processus nginx identifiés via `/proc//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 : + +| 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 | + +**Filtrage par PID Apache** : La map `apache_pid_map` ne permet que les processus Apache httpd identifiés via `/proc//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) 2. `accept_map[{pid_tgid, fd}]` — cache accept4 (tracepoint kernel) diff --git a/services/ja4ebpf/cmd/ja4ebpf/main.go b/services/ja4ebpf/cmd/ja4ebpf/main.go index cf7a14d..85c85b9 100644 --- a/services/ja4ebpf/cmd/ja4ebpf/main.go +++ b/services/ja4ebpf/cmd/ja4ebpf/main.go @@ -64,11 +64,10 @@ type Config struct { } `yaml:"log"` Uprobes struct { - Enabled bool `yaml:"enabled"` // activer l'attachement automatique des uprobes - NginxBinPath string `yaml:"nginx_bin_path"` // chemin vers le binaire nginx - ApacheEnabled bool `yaml:"apache_enabled"` // activer la capture Apache httpd - MaxRetries int `yaml:"max_retries"` // nombre de tentatives d'attachement (défaut: 30) - RetryIntervalSec int `yaml:"retry_interval_sec"` // intervalle entre tentatives (défaut: 2) + Enabled bool `yaml:"enabled"` // activer l'attachement automatique des uprobes + Servers []string `yaml:"servers"` // serveurs web à capturer: ["nginx", "apache", "both"] + MaxRetries int `yaml:"max_retries"` // nombre de tentatives d'attachement (défaut: 30) + RetryIntervalSec int `yaml:"retry_interval_sec"` // intervalle entre tentatives (défaut: 2) } `yaml:"uprobes"` } @@ -89,8 +88,7 @@ func loadConfig(path string) (*Config, error) { cfg.Log.Level = "info" cfg.Log.Format = "json" cfg.Uprobes.Enabled = false - cfg.Uprobes.NginxBinPath = "/usr/sbin/nginx" - cfg.Uprobes.ApacheEnabled = false + cfg.Uprobes.Servers = []string{} // vide par défaut, peut être ["nginx"], ["apache"], ["nginx", "apache"] cfg.Uprobes.MaxRetries = 30 cfg.Uprobes.RetryIntervalSec = 2 @@ -140,11 +138,17 @@ func loadConfig(path string) (*Config, error) { if v := os.Getenv("JA4EBPF_UPROBES_ENABLED"); v != "" { cfg.Uprobes.Enabled = strings.EqualFold(v, "true") || v == "1" || v == "yes" } - if v := os.Getenv("JA4EBPF_NGINX_BIN_PATH"); v != "" { - cfg.Uprobes.NginxBinPath = v - } - if v := os.Getenv("JA4EBPF_APACHE_ENABLED"); v != "" { - cfg.Uprobes.ApacheEnabled = strings.EqualFold(v, "true") || v == "1" || v == "yes" + if v := os.Getenv("JA4EBPF_UPROBES_SERVERS"); v != "" { + // Accepte une liste séparée par des virgules: "nginx,apache" ou "both" pour les deux + if strings.EqualFold(v, "both") { + cfg.Uprobes.Servers = []string{"nginx", "apache"} + } else { + cfg.Uprobes.Servers = strings.Split(v, ",") + // Normaliser les noms de serveurs (trim whitespace et lowercase) + for i, s := range cfg.Uprobes.Servers { + cfg.Uprobes.Servers[i] = strings.ToLower(strings.TrimSpace(s)) + } + } } @@ -295,15 +299,21 @@ func main() { log.Printf("[ja4ebpf] avertissement tracepoint accept4: %v", err) } - // --- 4b. Attachement uprobes nginx (avec retry automatique) --- - if err := attachNginxUprobesWithRetry(ctx, ldr, cfg); err != nil { - log.Printf("[ja4ebpf] erreur attachement uprobes nginx: %v", err) - } - - // --- 4c. Attachement uprobes Apache httpd --- - if cfg.Uprobes.ApacheEnabled { - if err := attachApacheUprobesWithRetry(ctx, ldr, cfg); err != nil { - log.Printf("[ja4ebpf] erreur attachement uprobes Apache: %v", err) + // --- 4b. Attachement uprobes serveurs web (nginx, apache) --- + if cfg.Uprobes.Enabled && len(cfg.Uprobes.Servers) > 0 { + for _, server := range cfg.Uprobes.Servers { + switch server { + case "nginx": + if err := attachNginxUprobesWithRetry(ctx, ldr, cfg); err != nil { + log.Printf("[ja4ebpf] erreur attachement uprobes nginx: %v", err) + } + case "apache": + if err := attachApacheUprobesWithRetry(ctx, ldr, cfg); err != nil { + log.Printf("[ja4ebpf] erreur attachement uprobes Apache: %v", err) + } + default: + log.Printf("[ja4ebpf] avertissement: serveur web inconnu '%s' (options: nginx, apache)", server) + } } } @@ -351,9 +361,14 @@ func main() { // --- 8. Compteurs d'événements consommés (mode debug) --- consumed := &eventCounters{} - go consumeNginxHTTPEvents(ctx, ldr.NginxHTTPReader, mgr, &consumed.nginx) - if cfg.Uprobes.ApacheEnabled { - go consumeApacheHTTPEvents(ctx, ldr.ApacheHTTPReader, mgr, &consumed.apache) + // Démarrer les goroutines de consommation pour chaque serveur configuré + for _, server := range cfg.Uprobes.Servers { + switch server { + case "nginx": + go consumeNginxHTTPEvents(ctx, ldr.NginxHTTPReader, mgr, &consumed.nginx) + case "apache": + go consumeApacheHTTPEvents(ctx, ldr.ApacheHTTPReader, mgr, &consumed.apache) + } } // --- 9. Goroutines de consommation des ring buffers --- @@ -1225,14 +1240,9 @@ func updateH2Settings(last *correlation.HTTPRequest, settings *parser.HTTP2Setti // Retente jusqu'à maxRetries fois toutes les retryInterval secondes. // Utile pour attendre que nginx démarre après ja4ebpf. func attachNginxUprobesWithRetry(ctx context.Context, l *loader.Loader, cfg *Config) error { - if !cfg.Uprobes.Enabled { - log.Printf("[uprobes] nginx uprobes désactivés (uprobes.enabled=false)") - return nil - } - - binPath := cfg.Uprobes.NginxBinPath maxRetries := cfg.Uprobes.MaxRetries retryInterval := time.Duration(cfg.Uprobes.RetryIntervalSec) * time.Second + binPath := "/usr/sbin/nginx" // chemin par défaut pour nginx log.Printf("[uprobes] tentative d'attachement nginx uprobes (bin=%s, max_retries=%d, interval=%v)", binPath, maxRetries, retryInterval) @@ -1275,10 +1285,9 @@ func attachNginxUprobesWithRetry(ctx context.Context, l *loader.Loader, cfg *Con // Retente jusqu'à maxRetries fois toutes les retryInterval secondes. // Utile pour attendre que Apache httpd démarre après ja4ebpf. func attachApacheUprobesWithRetry(ctx context.Context, l *loader.Loader, cfg *Config) error { - if !cfg.Uprobes.ApacheEnabled { - log.Printf("[uprobes] Apache uprobes désactivés (uprobes.apache_enabled=false)") - return nil - } + maxRetries := cfg.Uprobes.MaxRetries + retryInterval := time.Duration(cfg.Uprobes.RetryIntervalSec) * time.Second + maxRetries := cfg.Uprobes.MaxRetries retryInterval := time.Duration(cfg.Uprobes.RetryIntervalSec) * time.Second