#!/usr/bin/env bash # ============================================================================= # run-tests-vm.sh — Tests ja4ebpf multi-stack dans une VM Vagrant # # Architecture : # Phase 1 (dans la VM) : démarrer ClickHouse, serveur web, ja4ebpf # Phase 2 (depuis le host) : générer du trafic vers l'IP eth0 de la VM # Phase 3 (dans la VM) : vérifier les données dans ClickHouse # # Stacks supportées : # nginx — nginx avec TLS (HTTP/1.1 + HTTP/2) # apache — Apache httpd avec TLS (HTTP/1.1 + HTTP/2) # hitch-varnish — hitch (TLS) → Varnish (cache/H2) → backend Python # all-ips — 3 services simultanés, 1 IP chacun (nginx IP1, apache IP2, hitch+varnish IP3) # all — exécute les 3 stacks séquentiellement # # Modes : # start — démarrer les services (Phase 1) # verify — vérifier les données (Phase 3) # (défaut) — start + verify (le trafic doit être généré entre les deux) # # Usage (depuis le host via Makefile) : # make test-vm-nginx # make test-vm-apache # make test-vm-hitch-varnish # make test-vm-matrix # ============================================================================= set -euo pipefail export PATH="/usr/local/bin:/usr/local/go/bin:$PATH" STACK="${1:-nginx}" MODE="${2:-full}" # start | verify | full KEEP_RUNNING="${KEEP_RUNNING:-false}" PROJECT="/ja4-platform" GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; RESET='\033[0m' BOLD='\033[1m' log() { echo -e "${BOLD}[$STACK]${RESET} $(date +%H:%M:%S) $*"; } pass() { echo -e " ${GREEN}PASS${RESET} $*"; ((PASS_COUNT++)) || true; } fail() { echo -e " ${RED}FAIL${RESET} $*"; ((FAIL_COUNT++)) || true; } warn() { echo -e " ${YELLOW}WARN${RESET} $*"; ((WARN_COUNT++)) || true; } PASS_COUNT=0; FAIL_COUNT=0; WARN_COUNT=0 # ── Helpers communs ────────────────────────────────────────────────────────── # IPs des services (positionnées par setup_all_ips ou defaults à l'IP eth0) IP1="" # nginx IP2="" # apache IP3="" # hitch+varnish setup_all_ips() { local eth0_ip eth0_ip=$(get_eth0_ip) # Utiliser eth1 (réseau ja4-e2e, 192.168.42.0/24) pour les 3 IPs de service. # eth0 est le réseau vagrant-libvirt (DHCP, IPs dynamiques) — les alias IPs # ne sont pas routés par le dnsmasq de libvirt et sont injoignables depuis le host. # eth1 est le réseau ja4-e2e dédié — accessible par toutes les VMs et le host. local eth1_ip eth1_ip=$(ip -4 addr show eth1 2>/dev/null | awk '/inet / {sub(/\/.*/, "", $2); print $2; exit}') if [ -z "$eth1_ip" ]; then # Fallback: utiliser eth0 avec des alias si eth1 n'existe pas local net_prefix net_prefix=$(echo "$eth0_ip" | awk -F. '{print $1"."$2"."$3}') local base_last base_last=$(echo "$eth0_ip" | awk -F. '{print $4}') IP1="$eth0_ip" IP2="${net_prefix}.$((base_last + 100))" IP3="${net_prefix}.$((base_last + 101))" ip addr add "${IP2}/24" dev eth0 2>/dev/null || true ip addr add "${IP3}/24" dev eth0 2>/dev/null || true else # Utiliser eth1 (réseau ja4-e2e) pour les 3 services local net_prefix net_prefix=$(echo "$eth1_ip" | awk -F. '{print $1"."$2"."$3}') local base_last base_last=$(echo "$eth1_ip" | awk -F. '{print $4}') IP1="$eth1_ip" IP2="${net_prefix}.$((base_last + 50))" IP3="${net_prefix}.$((base_last + 51))" # Ajouter les alias IPs sur eth1 (idempotent) ip addr add "${IP2}/24" dev eth1 2>/dev/null || true ip addr add "${IP3}/24" dev eth1 2>/dev/null || true fi log "IPs services : IP1=${IP1} (nginx) IP2=${IP2} (apache) IP3=${IP3} (hitch+varnish)" } # Écrire les IPs dans /tmp pour que l'orchestrateur puisse les lire write_ip_manifest() { cat > /tmp/e2e-endpoint-ips.json << EOF {"ip1":"${IP1}","ip2":"${IP2}","ip3":"${IP3}"} EOF } gen_tls_cert() { local name="$1" openssl req -x509 -nodes -days 365 -subj "/CN=platform.test" \ -newkey rsa:2048 \ -keyout "/etc/pki/tls/private/${name}.key" \ -out "/etc/pki/tls/certs/${name}.crt" 2>/dev/null } setup_docroot() { local stack_name="${1:-$STACK}" mkdir -p /var/www/html echo '{"status":"ok","stack":"'"$stack_name"'"}' > /var/www/html/health for p in data api/users api/data/test; do mkdir -p "/var/www/html/$(dirname $p)" echo '{"ok":true}' > "/var/www/html/$p" done } get_eth0_ip() { ip -4 addr show eth0 | awk '/inet / {sub(/\/.*/, "", $2); print $2; exit}' 2>/dev/null || echo "" } # ── ClickHouse ──────────────────────────────────────────────────────────────── start_clickhouse() { # Si un ClickHouse externe est configuré, ne pas démarrer le conteneur local if [ -n "${CH_HOST:-}" ] && [ "$CH_HOST" != "127.0.0.1" ] && [ "$CH_HOST" != "localhost" ]; then log "ClickHouse externe ($CH_HOST) — démarrage local ignoré" # Vérifier que le ClickHouse distant est accessible for i in $(seq 1 30); do curl -sf "http://${CH_HOST}:8123/ping" >/dev/null 2>&1 && { pass "ClickHouse distant prêt"; return 0; } sleep 2 done fail "ClickHouse distant ($CH_HOST) inaccessible"; return 1 fi log "Démarrage ClickHouse..." docker rm -f ja4-clickhouse 2>/dev/null || true CSV_DIR="$PROJECT/tests/integration/platform/csv-stubs" docker run -d --name ja4-clickhouse \ -p 8123:8123 -p 9000:9000 \ -e CLICKHOUSE_DB=ja4_processing \ -e CLICKHOUSE_USER=default \ -e CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 \ -v "$PROJECT/tests/integration/platform/clickhouse-init.sh:/docker-entrypoint-initdb.d/00_init.sh" \ -v "$CSV_DIR:/var/lib/clickhouse/user_files" \ $(for f in "$PROJECT/shared/clickhouse/"*.sql; do echo "-v $f:/initdb-src/$(basename $f):ro" done) \ clickhouse/clickhouse-server:24.8 2>&1 | tail -1 log "Attente ClickHouse (max 120s)..." for i in $(seq 1 60); do curl -sf "http://localhost:8123/ping" >/dev/null 2>&1 && { pass "ClickHouse prêt"; return 0; } sleep 2 done fail "ClickHouse timeout"; exit 1 } # ── ja4ebpf ──────────────────────────────────────────────────────────────────── start_ja4ebpf() { log "Démarrage ja4ebpf..." pkill ja4ebpf 2>/dev/null || true sleep 1 local ssl_lib="" for lib in /usr/lib64/libssl.so.3 /usr/lib64/libssl.so.1.1 /usr/lib/libssl.so.3 /usr/lib/libssl.so.1.1; do [ -f "$lib" ] && { ssl_lib="$lib"; break; } done [ -z "$ssl_lib" ] && ssl_lib="/usr/lib64/libssl.so.3" local ch_addr="${CH_HOST:-127.0.0.1}" cat > /tmp/ja4ebpf.yml << EOF interfaces: - any ssl_lib_path: "${ssl_lib}" listen_ports: - 80 - 443 clickhouse: dsn: "clickhouse://default:@${ch_addr}:9000/ja4_logs?async_insert=0" batch_size: 100 flush_secs: 1 correlation: timeout_ms: 500 slowloris_ms: 10000 log: level: "info" format: "json" EOF JA4EBPF_CONFIG=/tmp/ja4ebpf.yml ja4ebpf > /tmp/ja4ebpf.log 2>&1 & JA4EBPF_PID=$! sleep 3 if ! kill -0 "$JA4EBPF_PID" 2>/dev/null; then fail "ja4ebpf s'est arrêté immédiatement" tail -10 /tmp/ja4ebpf.log return 1 fi log "ja4ebpf démarré (PID $JA4EBPF_PID)" # Vérifier TC ingress sur les interfaces local TC_IFACES=0 for IFACE in $(ls /sys/class/net/ 2>/dev/null | grep -v lo); do if tc filter show dev "$IFACE" ingress 2>/dev/null | grep -qi "bpf\|direct-action"; then TC_IFACES=$((TC_IFACES + 1)) fi done if [ "$TC_IFACES" -gt 0 ]; then pass "TC ingress attaché sur $TC_IFACES interface(s)" else warn "Aucun TC ingress détecté" bpftool prog show name capture_tc 2>/dev/null || true fi } # ═════════════════════════════════════════════════════════════════════════════ # Stack : nginx # ═════════════════════════════════════════════════════════════════════════════ setup_nginx() { log "Configuration nginx avec TLS..." gen_tls_cert nginx setup_docroot nginx cp "$PROJECT/tests/integration/nginx/platform/nginx.conf" /etc/nginx/nginx.conf # Binder sur IP1 si en mode multi-IP local bind_addr="${IP1:-}" if [ -n "$bind_addr" ]; then # Reset : remettre les directives listen à leur valeur par défaut avant de binder # (si un run précédent a déjà remplacé par une IP, le sed suivant ne matcherait pas) sed -i 's/^listen [0-9.]*:80;/listen 80;/' /etc/nginx/nginx.conf sed -i 's/^listen [0-9.]*:443 ssl http2;/listen 443 ssl http2;/' /etc/nginx/nginx.conf sed -i "s/listen 80;/listen ${bind_addr}:80;/" /etc/nginx/nginx.conf sed -i "s/listen 443 ssl http2;/listen ${bind_addr}:443 ssl http2;/" /etc/nginx/nginx.conf fi mkdir -p /run/nginx nginx -t && nginx for i in $(seq 1 20); do curl -sf "http://${IP1:-localhost}/health" >/dev/null 2>&1 && break sleep 0.5 done pass "nginx démarré (IP ${IP1:-*})" } stop_nginx() { nginx -s stop 2>/dev/null || true; } # ═════════════════════════════════════════════════════════════════════════════ # Stack : apache # ═════════════════════════════════════════════════════════════════════════════ setup_apache() { # Vérifier que httpd est disponible if ! command -v httpd >/dev/null 2>&1; then warn "httpd non disponible — apache ignoré" return 0 fi log "Configuration Apache httpd avec TLS..." gen_tls_cert apache setup_docroot apache if command -v httpd >/dev/null 2>&1; then if ! httpd -M 2>/dev/null | grep -q http2_module; then echo "LoadModule http2_module modules/mod_http2.so" \ >> /etc/httpd/conf.modules.d/00-base.conf 2>/dev/null || true fi fi mkdir -p /run/httpd /var/log/httpd cp "$PROJECT/tests/integration/apache/platform/httpd-ssl.conf" \ /etc/httpd/conf.d/ssl.conf 2>/dev/null || true # Binder sur IP2 si en mode multi-IP local bind_addr="${IP2:-}" if [ -n "$bind_addr" ]; then # Reset : remettre les directives Listen/VirtualHost à leur valeur par défaut # (si un run précédent a déjà remplacé par une IP, le sed suivant ne matcherait pas) sed -i 's/^Listen [0-9.]*:80$/Listen 80/' /etc/httpd/conf/httpd.conf sed -i 's/^Listen [0-9.]*:443 https$/Listen 443 https/' /etc/httpd/conf.d/ssl.conf sed -i 's///' /etc/httpd/conf.d/ssl.conf sed -i 's///' /etc/httpd/conf.d/ssl.conf # Appliquer les bindings IP2 sed -i "s/^Listen 80$/Listen ${bind_addr}:80/" /etc/httpd/conf/httpd.conf sed -i "s/^Listen 443 https/Listen ${bind_addr}:443 https/" /etc/httpd/conf.d/ssl.conf sed -i "s///" /etc/httpd/conf.d/ssl.conf sed -i "s///" /etc/httpd/conf.d/ssl.conf 2>/dev/null || true # S'assurer qu'il n'y a pas de Listen IP2:80 en double dans ssl.conf # (le Listen 80 est déjà dans httpd.conf, pas besoin de le remettre dans ssl.conf) sed -i "/^Listen ${bind_addr}:80$/d" /etc/httpd/conf.d/ssl.conf fi httpd -t 2>&1 && httpd sleep 2 for i in $(seq 1 20); do curl -sf "http://${IP2:-localhost}/health" >/dev/null 2>&1 && break sleep 0.5 done pass "Apache httpd démarré (IP ${IP2:-*})" } stop_apache() { pkill httpd 2>/dev/null || true; } # ═════════════════════════════════════════════════════════════════════════════ # Stack : hitch + varnish # ═════════════════════════════════════════════════════════════════════════════ setup_hitch_varnish() { # Vérifier que hitch est disponible if ! command -v hitch >/dev/null 2>&1; then warn "hitch non disponible — hitch+varnish ignoré" return 0 fi log "Configuration hitch + Varnish..." gen_tls_cert hitch mkdir -p /etc/hitch cat /etc/pki/tls/private/hitch.key /etc/pki/tls/certs/hitch.crt \ > /etc/hitch/hitch.pem # Binder hitch sur IP3 si en mode multi-IP, sinon [*]:443 local hitch_bind="${IP3:-*}" cat > /etc/hitch/hitch.conf << HCONF frontend = "[${hitch_bind}]:443" backend = "[127.0.0.1]:6081" pem-file = "/etc/hitch/hitch.pem" write-proxy-v1 = on tls-protos = TLSv1.2 TLSv1.3 ciphers = "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256" alpn-protos = "h2,http/1.1" workers = 2 user = "nobody" daemon = off log-level = 1 syslog = off HCONF mkdir -p /etc/varnish cp "$PROJECT/tests/integration/hitch-varnish/platform/varnish.vcl" \ /etc/varnish/default.vcl 2>/dev/null || { cat > /etc/varnish/default.vcl << 'VCL' vcl 4.1; backend default { .host = "127.0.0.1"; .port = "8080"; } sub vcl_deliver { set resp.http.Via = "1.1 varnish"; set resp.http.X-Client-IP = client.ip; } VCL } setup_docroot hitch-varnish # Backend HTTP (port 8080) python3 -c " import http.server, socketserver, json class H(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def do_GET(self): body = json.dumps({'status':'ok','stack':'hitch-varnish','path':self.path}).encode() self.send_response(200) self.send_header('Content-Type','application/json') self.send_header('Content-Length',len(body)) self.end_headers() self.wfile.write(body) def do_POST(self): n = int(self.headers.get('Content-Length',0)) self.rfile.read(n) body = b'{\"result\":\"accepted\"}' self.send_response(200) self.send_header('Content-Type','application/json') self.send_header('Content-Length',len(body)) self.end_headers() self.wfile.write(body) with socketserver.TCPServer(('127.0.0.1', 8080), H) as s: s.serve_forever() " & sleep 1 # HTTP sur IP3:80 (backend dédié pour le trafic HTTP en clair) if [ -n "${IP3:-}" ]; then python3 -c " import http.server, socketserver, json class H(http.server.BaseHTTPRequestHandler): def log_message(self, *a): pass def do_GET(self): body = json.dumps({'status':'ok','stack':'hitch-varnish','path':self.path}).encode() self.send_response(200) self.send_header('Content-Type','application/json') self.send_header('Content-Length',len(body)) self.end_headers() self.wfile.write(body) with socketserver.TCPServer(('${IP3}', 80), H) as s: s.serve_forever() " & sleep 1 fi varnishd -f /etc/varnish/default.vcl \ -a "127.0.0.1:6081,PROXY" \ -p feature=+http2 \ -s malloc,64m \ -T 127.0.0.1:6082 2>/dev/null sleep 2 nohup hitch --config=/etc/hitch/hitch.conf >/dev/null 2>&1 & sleep 2 for i in $(seq 1 20); do curl -skf "https://${IP3:-localhost}/health" >/dev/null 2>&1 && break sleep 0.5 done pass "hitch + Varnish démarrés (IP ${IP3:-*})" } stop_hitch_varnish() { pkill hitch 2>/dev/null || true pkill varnishd 2>/dev/null || true pkill -f "TCPServer.*8080" 2>/dev/null || true pkill -f "TCPServer.*':80'" 2>/dev/null || true } # ═════════════════════════════════════════════════════════════════════════════ # Vérification ClickHouse # ═════════════════════════════════════════════════════════════════════════════ verify_db() { log "Vérification des données dans ClickHouse..." ch_val() { local ch_http_host="${CH_HOST:-localhost}" curl -sf "http://${ch_http_host}:8123/?database=ja4_logs" \ --data-urlencode "query=$1" 2>/dev/null | tr -d ' \n' || echo "0" } # Attendre que http_logs_raw contienne des données (max 30s) local raw_ok=false log " Attente données brutes dans ClickHouse..." for i in $(seq 1 15); do local raw_count raw_count=$(ch_val "SELECT count() FROM http_logs_raw") if [ "${raw_count:-0}" -gt 0 ] 2>/dev/null; then pass "http_logs_raw : $raw_count lignes (${i}*2s)" raw_ok=true break fi sleep 2 done if [ "$raw_ok" = "false" ]; then fail "http_logs_raw vide — ja4ebpf n'a rien capturé" log " Logs ja4ebpf :" tail -10 /tmp/ja4ebpf.log 2>/dev/null | sed 's/^/ /' fi # Attendre que la MV http_logs se remplisse (max 30s) local logs_ok=false log " Attente MV http_logs..." for i in $(seq 1 15); do local logs_count logs_count=$(ch_val "SELECT count() FROM http_logs") if [ "${logs_count:-0}" -gt 0 ] 2>/dev/null; then logs_ok=true break fi sleep 2 done if [ "$logs_ok" = "false" ]; then warn "MV http_logs vide après 30s — vérification partielle uniquement" fi # L3/L4 ttl=$(ch_val "SELECT count() FROM http_logs WHERE ip_meta_ttl > 0") [ "${ttl:-0}" -gt 0 ] 2>/dev/null && pass "L3/L4 TTL ($ttl)" || fail "L3/L4 TTL absent" mss=$(ch_val "SELECT count() FROM http_logs WHERE tcp_meta_mss > 0") [ "${mss:-0}" -gt 0 ] 2>/dev/null && pass "TCP MSS ($mss)" || fail "TCP MSS absent" # TLS ja4=$(ch_val "SELECT count() FROM http_logs WHERE ja4 != ''") [ "${ja4:-0}" -gt 0 ] 2>/dev/null && pass "JA4 fingerprint ($ja4)" || fail "JA4 absent" sni=$(ch_val "SELECT count() FROM http_logs WHERE tls_sni != ''") [ "${sni:-0}" -gt 0 ] 2>/dev/null && pass "TLS SNI ($sni)" || warn "TLS SNI absent" # L7 HTTP method=$(ch_val "SELECT count() FROM http_logs WHERE method != ''") [ "${method:-0}" -gt 0 ] 2>/dev/null && pass "L7 HTTP ($method)" || fail "L7 HTTP ABSENT" path=$(ch_val "SELECT count() FROM http_logs WHERE path != ''") [ "${path:-0}" -gt 0 ] 2>/dev/null && pass "L7 path ($path)" || fail "L7 path absent" status=$(ch_val "SELECT count() FROM http_logs WHERE status_code > 0") [ "${status:-0}" -gt 0 ] 2>/dev/null && pass "status_code ($status)" || warn "status_code absent" methods=$(ch_val "SELECT groupArray(method) FROM (SELECT DISTINCT method FROM http_logs WHERE method != '')") log "Méthodes HTTP : $methods" total=$(ch_val "SELECT count() FROM http_logs") pass "Total http_logs : $total" } # ═════════════════════════════════════════════════════════════════════════════ # Nettoyage # ═════════════════════════════════════════════════════════════════════════════ stop_stack() { pkill ja4ebpf 2>/dev/null || true case "$STACK" in nginx) stop_nginx ;; apache) stop_apache ;; hitch-varnish) stop_hitch_varnish ;; all-ips) stop_nginx; stop_apache; stop_hitch_varnish; remove_alias_ips ;; esac # Ne pas supprimer le ClickHouse s'il est externe (VM analysis) if [ -z "${CH_HOST:-}" ] || [ "$CH_HOST" = "127.0.0.1" ] || [ "$CH_HOST" = "localhost" ]; then docker rm -f ja4-clickhouse 2>/dev/null || true fi } remove_alias_ips() { # Déterminer l'interface des IPs alias (eth1 si réseau ja4-e2e, eth0 sinon) local iface="eth0" if [ -n "${IP2:-}" ]; then # Si IP2 commence par 192.168.42, c'est sur eth1 case "$IP2" in 192.168.42.*) iface="eth1" ;; esac fi if [ -n "${IP2:-}" ]; then ip addr del "${IP2}/24" dev "$iface" 2>/dev/null || true fi if [ -n "${IP3:-}" ]; then ip addr del "${IP3}/24" dev "$iface" 2>/dev/null || true fi } cleanup() { if [ "$KEEP_RUNNING" != "true" ]; then # En mode E2E distribué (CH_HOST externe), l'orchestrateur gère le nettoyage. # On ne nettoie que si le script est lancé en mode standalone. if [ -n "${CH_HOST:-}" ] && [ "$CH_HOST" != "127.0.0.1" ] && [ "$CH_HOST" != "localhost" ]; then log "Nettoyage ignoré (mode distribué — géré par l'orchestrateur)" else log "Nettoyage..." stop_stack fi fi } trap cleanup EXIT # ═════════════════════════════════════════════════════════════════════════════ # Phase 1 : démarrage des services # ═════════════════════════════════════════════════════════════════════════════ do_start() { echo "" echo "╔══════════════════════════════════════════╗" echo "║ Phase 1 : Démarrage — $STACK" echo "╚══════════════════════════════════════════╝" echo "" # Vérifier prérequis command -v ja4ebpf >/dev/null 2>&1 || { log "Rebuild ja4ebpf..." cd "$PROJECT/services/ja4ebpf" GOWORK=off go generate ./internal/loader/ 2>&1 | tail -3 GOWORK=off CGO_ENABLED=0 go build -o /tmp/ja4ebpf_new ./cmd/ja4ebpf/ && mv /tmp/ja4ebpf_new /usr/local/bin/ja4ebpf } # Docker n'est nécessaire que pour un ClickHouse local if [ -z "${CH_HOST:-}" ] || [ "$CH_HOST" = "127.0.0.1" ] || [ "$CH_HOST" = "localhost" ]; then command -v docker >/dev/null 2>&1 || { fail "Docker non installé"; exit 1; } fi start_clickhouse case "$STACK" in nginx) setup_nginx ;; apache) setup_apache ;; hitch-varnish) setup_hitch_varnish ;; all-ips) setup_all_ips; setup_nginx; setup_apache; setup_hitch_varnish; write_ip_manifest ;; *) fail "Stack inconnue: $STACK"; exit 1 ;; esac start_ja4ebpf # Afficher l'IP pour le host local eth0_ip eth0_ip=$(get_eth0_ip) echo "" if [ "$STACK" = "all-ips" ]; then echo " ┌─────────────────────────────────────────────┐" echo " │ Services prêts ! │" echo " │ nginx : http://${IP1}:80 https://${IP1}:443" echo " │ apache : http://${IP2}:80 https://${IP2}:443" echo " │ hitch+varnish : http://${IP3}:80 https://${IP3}:443" echo " └─────────────────────────────────────────────┘" else echo " ┌─────────────────────────────────────────┐" echo " │ Services prêts ! │" echo " │ IP eth0 : $eth0_ip" echo " │ HTTP : http://$eth0_ip:80" echo " │ HTTPS : https://$eth0_ip:443" echo " └─────────────────────────────────────────┘" fi echo "" } # ═════════════════════════════════════════════════════════════════════════════ # Phase 3 : vérification # ═════════════════════════════════════════════════════════════════════════════ do_verify() { echo "" echo "╔══════════════════════════════════════════╗" echo "║ Phase 3 : Vérification — $STACK" echo "╚══════════════════════════════════════════╝" echo "" verify_db echo "" echo "════════════════════════════════════════════" echo -e " ${GREEN}OK${RESET}: $PASS_COUNT ${YELLOW}WARN${RESET}: $WARN_COUNT ${RED}FAIL${RESET}: $FAIL_COUNT" if [ "$FAIL_COUNT" -eq 0 ]; then echo -e " ${GREEN}${BOLD}$STACK : Tous les tests réussis !${RESET}" else echo -e " ${RED}${BOLD}$STACK : $FAIL_COUNT tests échoués${RESET}" tail -20 /tmp/ja4ebpf.log 2>/dev/null || true fi } # ═════════════════════════════════════════════════════════════════════════════ # Main # ═════════════════════════════════════════════════════════════════════════════ case "$MODE" in start) do_start echo " En attente de trafic depuis le host..." # Attendre que le host génère le trafic # Le fichier /tmp/ja4ebpf-traffic-done est créé par le host après le trafic # En mode E2E distribué (CH_HOST externe), on attend sans limite de temps if [ -n "${CH_HOST:-}" ] && [ "$CH_HOST" != "127.0.0.1" ] && [ "$CH_HOST" != "localhost" ]; then while [ ! -f /tmp/ja4ebpf-traffic-done ]; do sleep 2; done else for i in $(seq 1 120); do [ -f /tmp/ja4ebpf-traffic-done ] && break sleep 1 done fi # En mode ClickHouse externe (E2E distribué), la vérification est faite # par le script orchestrateur (run-e2e-test.sh Phase 5). On saute la # vérification locale car les MV peuvent ne pas encore être peuplées. if [ -n "${CH_HOST:-}" ] && [ "$CH_HOST" != "127.0.0.1" ] && [ "$CH_HOST" != "localhost" ]; then log "ClickHouse externe — vérification locale ignorée (gérée par l'orchestrateur)" log "Logs ja4ebpf :" tail -5 /tmp/ja4ebpf.log 2>/dev/null | sed 's/^/ /' pass "ja4ebpf actif (ClickHouse externe)" else # Laisser le temps au pipeline ClickHouse de traiter les données brutes # (http_logs_raw → MV http_logs) avant de vérifier log "Attente pipeline ClickHouse (20s)..." sleep 20 do_verify fi ;; verify) do_verify ;; *) # Mode legacy : tout dans la VM (trafic local uniquement) # Note : XDP sur eth0 ne capturera PAS le trafic localhost do_start log "ATTENTION : le trafic localhost n'est pas capturé par XDP/eth0" log "Utilisez 'make test-vm-matrix' pour le test complet avec trafic host" # Générer quand même du trafic pour les uprobes for path in / /health; do curl -sf -k "https://localhost$path" >/dev/null 2>&1 || true done sleep 10 do_verify ;; esac [ "$FAIL_COUNT" -eq 0 ] && exit 0 || exit 1