refactor(ebpf): simplify web server configuration with server list

Refactor uprobes configuration to use a single server list instead
of separate nginx_bin_path and apache_enabled options.

Configuration changes:
- Uprobes.Servers: []string (was: NginxBinPath + ApacheEnabled)
  - Accepts: ["nginx"], ["apache"], or ["nginx", "apache"]
  - Can also use "both" to enable both servers
- Environment variable: JA4EBPF_UPROBES_SERVERS (was: separate vars)

Examples:
  YAML:
    uprobes:
      enabled: true
      servers: ["nginx", "apache"]

  Environment:
    JA4EBPF_UPROBES_SERVERS=nginx,apache

Code changes:
- Generic loop over cfg.Uprobes.Servers for attachment and consumption
- Remove duplicate checks for Enabled/ApacheEnabled
- Update attachNginxUprobesWithRetry to use default nginx path
- Update attachApacheUprobesWithRetry to remove ApacheEnabled check
- Update documentation to reflect both nginx and apache support

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-20 13:43:32 +02:00
parent 7dfe640003
commit cba1cca180
2 changed files with 55 additions and 37 deletions

View File

@ -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/<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 :
| 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/<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)
2. `accept_map[{pid_tgid, fd}]` — cache accept4 (tracepoint kernel)

View File

@ -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