fix(tests): rewrite test-rpm.sh for 3 distros × 3 stacks RPM validation
Complete rewrite of the RPM test harness to properly test ja4ebpf across CentOS 8, Rocky Linux 9/10 with nginx, apache, and hitch+varnish stacks. Key fixes: - Replace nested heredoc-over-SSH with upload-then-execute script pattern - Fix Apache SSLCertificateKey → SSLCertificateKeyFile directive - Fix hitch config: backend string syntax, user=nobody, combined PEM, --daemon - Fix timeout+shell-function incompatibility (timeout can't exec functions) - Fix subshell result counting by parsing output file instead of variables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
372
tests/vm/test-rpm.sh
Executable file
372
tests/vm/test-rpm.sh
Executable file
@ -0,0 +1,372 @@
|
||||
#!/usr/bin/env bash
|
||||
# test-rpm.sh — Test ja4ebpf via RPM sur les 3 distros × 3 stacks
|
||||
#
|
||||
# Usage:
|
||||
# make rpm-ja4ebpf && ./tests/vm/test-rpm.sh [VM] [STACK]
|
||||
# ./tests/vm/test-rpm.sh rocky9 nginx
|
||||
# ./tests/vm/test-rpm.sh all all
|
||||
#
|
||||
# VMs: centos8 rocky9 rocky10 (or "all")
|
||||
# Stacks: nginx apache hitch-varnish (or "all")
|
||||
set -euo pipefail
|
||||
|
||||
PROJECT_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
VM_DIR="$PROJECT_ROOT/tests/vm"
|
||||
RPM_BASE="$PROJECT_ROOT/services/ja4ebpf/dist/output"
|
||||
|
||||
# Timeouts (seconds)
|
||||
TEST_TIMEOUT=120 # max per VM×stack test
|
||||
SSH_TIMEOUT=90 # max per SSH command
|
||||
UPLOAD_TIMEOUT=60 # max for RPM upload
|
||||
|
||||
VM="${1:-rocky9}"
|
||||
STACK="${2:-nginx}"
|
||||
|
||||
# Wrapper vagrant — always runs from the Vagrantfile directory
|
||||
v() { (cd "$VM_DIR" && command vagrant "$@"); }
|
||||
|
||||
# SSH with timeout — uses bash -c to avoid timeout+function issue
|
||||
vssh() {
|
||||
local VM="$1"; shift
|
||||
timeout "$SSH_TIMEOUT" bash -c "cd '$VM_DIR' && vagrant ssh '$VM' -- \"\$@\"" -- "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
vm_to_el() {
|
||||
case "$1" in
|
||||
centos8) echo "el8" ;;
|
||||
rocky9) echo "el9" ;;
|
||||
rocky10) echo "el10" ;;
|
||||
*) echo "UNKNOWN" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
ALL_VMS="centos8 rocky9 rocky10"
|
||||
ALL_STACKS="nginx apache hitch-varnish"
|
||||
[ "$VM" = "all" ] && VMS="$ALL_VMS" || VMS="$VM"
|
||||
[ "$STACK" = "all" ] && STACKS="$ALL_STACKS" || STACKS="$STACK"
|
||||
|
||||
PASS=0; FAIL=0; SKIP=0; RESULTS=()
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ Test RPM ja4ebpf : VMS=$VMS STACKS=$STACKS"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
|
||||
# Check RPMs exist
|
||||
for vm in $VMS; do
|
||||
el=$(vm_to_el "$vm")
|
||||
rpm=$(ls -t $RPM_BASE/$el/ja4ebpf-*.rpm 2>/dev/null | head -1)
|
||||
if [ -z "$rpm" ]; then
|
||||
echo "ERROR: no RPM for $vm ($el) in $RPM_BASE/$el/"; echo "Run: make rpm-ja4ebpf"; exit 1
|
||||
fi
|
||||
echo " $vm → $(basename $rpm)"
|
||||
done
|
||||
|
||||
# ── Generate remote setup script ─────────────────────────────────────
|
||||
|
||||
generate_setup_script() {
|
||||
local STACK="$1"
|
||||
local SETUP_SCRIPT="/tmp/ja4-setup-$$$.sh"
|
||||
|
||||
cat > "$SETUP_SCRIPT" << 'SETUP_EOF'
|
||||
#!/bin/bash
|
||||
STACK="__STACK__"
|
||||
|
||||
# ── Stack functions ──────────────────────────────────────────────────
|
||||
|
||||
setup_nginx() {
|
||||
mkdir -p /run/nginx /var/www/html
|
||||
echo '{"ok":true}' > /var/www/html/health
|
||||
openssl req -x509 -nodes -days 365 -subj /CN=test -newkey rsa:2048 \
|
||||
-keyout /etc/pki/tls/private/test.key -out /etc/pki/tls/certs/test.pem 2>/dev/null || true
|
||||
cat > /etc/nginx/nginx.conf << 'NGINX_EOF'
|
||||
worker_processes 1;
|
||||
events { worker_connections 64; }
|
||||
http {
|
||||
access_log off;
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/pki/tls/certs/test.pem;
|
||||
ssl_certificate_key /etc/pki/tls/private/test.key;
|
||||
root /var/www/html;
|
||||
}
|
||||
}
|
||||
NGINX_EOF
|
||||
nginx && echo " nginx ready"
|
||||
}
|
||||
|
||||
setup_apache() {
|
||||
mkdir -p /var/www/html
|
||||
echo '{"ok":true}' > /var/www/html/health
|
||||
openssl req -x509 -nodes -days 365 -subj /CN=test -newkey rsa:2048 \
|
||||
-keyout /etc/pki/tls/private/test.key -out /etc/pki/tls/certs/test.pem 2>/dev/null || true
|
||||
[ -f /etc/httpd/conf.d/ssl.conf ] && mv /etc/httpd/conf.d/ssl.conf /etc/httpd/conf.d/ssl.conf.disabled 2>/dev/null || true
|
||||
cat > /etc/httpd/conf.d/test.conf << 'APACHE_EOF'
|
||||
Listen 8080
|
||||
<VirtualHost *:8080>
|
||||
DocumentRoot /var/www/html
|
||||
</VirtualHost>
|
||||
Listen 8443
|
||||
<VirtualHost *:8443>
|
||||
DocumentRoot /var/www/html
|
||||
SSLEngine on
|
||||
SSLCertificateFile /etc/pki/tls/certs/test.pem
|
||||
SSLCertificateKeyFile /etc/pki/tls/private/test.key
|
||||
</VirtualHost>
|
||||
APACHE_EOF
|
||||
httpd && echo " httpd ready"
|
||||
}
|
||||
|
||||
setup_hitch_varnish() {
|
||||
mkdir -p /var/www/html
|
||||
echo '{"ok":true}' > /var/www/html/health
|
||||
openssl req -x509 -nodes -days 365 -subj /CN=test -newkey rsa:2048 \
|
||||
-keyout /etc/pki/tls/private/test.key -out /etc/pki/tls/certs/test.pem 2>/dev/null || true
|
||||
|
||||
# Start simple HTTP backend
|
||||
nohup python3 -c 'import http.server, os; os.chdir("/var/www/html"); http.server.HTTPServer(("127.0.0.1", 8081), http.server.SimpleHTTPRequestHandler).serve_forever()' </dev/null >/dev/null 2>&1 &
|
||||
sleep 1
|
||||
|
||||
# Varnish
|
||||
mkdir -p /etc/varnish
|
||||
cat > /etc/varnish/default.vcl << 'VARNISH_EOF'
|
||||
vcl 4.1;
|
||||
backend default { .host = "127.0.0.1"; .port = "8081"; }
|
||||
VARNISH_EOF
|
||||
varnishd -a :80 -f /etc/varnish/default.vcl -s malloc,64m 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# Hitch (combined PEM: key + cert)
|
||||
mkdir -p /etc/hitch
|
||||
cat /etc/pki/tls/private/test.key /etc/pki/tls/certs/test.pem > /etc/pki/tls/private/hitch.pem
|
||||
cat > /etc/hitch/hitch.conf << 'HITCH_EOF'
|
||||
frontend = {
|
||||
host = "0.0.0.0"
|
||||
port = "443"
|
||||
tls = on
|
||||
pem-file = "/etc/pki/tls/private/hitch.pem"
|
||||
}
|
||||
backend = "[127.0.0.1]:80"
|
||||
user = "nobody"
|
||||
HITCH_EOF
|
||||
hitch --daemon --config=/etc/hitch/hitch.conf >/dev/null 2>&1 && echo ' hitch started' || echo ' hitch failed'
|
||||
sleep 1
|
||||
echo " hitch+varnish ready"
|
||||
}
|
||||
|
||||
# ── Main setup ──────────────────────────────────────────────────────
|
||||
|
||||
# [A] Install RPM
|
||||
echo " [A] Install RPM..."
|
||||
pkill ja4ebpf 2>/dev/null || true; sleep 1
|
||||
yum remove -y ja4ebpf 2>/dev/null || true
|
||||
yum install -y /tmp/ja4ebpf-test.rpm 2>&1 | tail -3
|
||||
[ -x /usr/sbin/ja4ebpf ] && echo " binary OK" || { echo " FAIL: binary not found"; exit 1; }
|
||||
|
||||
# [B] Configure
|
||||
echo " [B] Configure..."
|
||||
mkdir -p /etc/ja4ebpf
|
||||
cat > /etc/ja4ebpf/config.yml << 'CONF_EOF'
|
||||
interface: eth0
|
||||
ssl_lib_path: "/usr/lib64/libssl.so.3"
|
||||
debug: true
|
||||
clickhouse:
|
||||
dsn: "clickhouse://default:@127.0.0.1:9000/ja4_logs"
|
||||
batch_size: 50
|
||||
flush_secs: 1
|
||||
correlation:
|
||||
timeout_ms: 500
|
||||
slowloris_ms: 10000
|
||||
log:
|
||||
level: "debug"
|
||||
format: "text"
|
||||
CONF_EOF
|
||||
|
||||
# [C] Start web stack
|
||||
echo " [C] Stack: $STACK..."
|
||||
nginx -s stop 2>/dev/null || true
|
||||
httpd -k stop 2>/dev/null || true
|
||||
pkill varnishd 2>/dev/null || true
|
||||
pkill hitch 2>/dev/null || true
|
||||
pkill -f 'python3 -c' 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
case "$STACK" in
|
||||
nginx) setup_nginx ;;
|
||||
apache) setup_apache ;;
|
||||
hitch-varnish) setup_hitch_varnish ;;
|
||||
esac
|
||||
|
||||
# Open firewall
|
||||
firewall-cmd --add-service=http --add-service=https 2>/dev/null || true
|
||||
firewall-cmd --add-port=8080/tcp --add-port=8443/tcp 2>/dev/null || true
|
||||
|
||||
# [D] Start ja4ebpf
|
||||
echo " [D] Start ja4ebpf..."
|
||||
JA4EBPF_CONFIG=/etc/ja4ebpf/config.yml /usr/sbin/ja4ebpf > /tmp/ja4-test.log 2>&1 &
|
||||
JA4PID=$!
|
||||
sleep 3
|
||||
|
||||
if ! kill -0 $JA4PID 2>/dev/null; then
|
||||
echo " FAIL: ja4ebpf crashed"
|
||||
cat /tmp/ja4-test.log
|
||||
exit 1
|
||||
fi
|
||||
echo " PID=$JA4PID"
|
||||
|
||||
# Check TC ingress filter
|
||||
if tc filter show dev eth0 ingress 2>/dev/null | grep -qi "bpf\|direct-action"; then
|
||||
echo " TC: attached"
|
||||
else
|
||||
echo " WARN: TC filter not detected"
|
||||
fi
|
||||
SETUP_EOF
|
||||
|
||||
# Replace stack placeholder
|
||||
sed -i "s/__STACK__/$STACK/" "$SETUP_SCRIPT"
|
||||
echo "$SETUP_SCRIPT"
|
||||
}
|
||||
|
||||
# ── Test one VM×stack combination ───────────────────────────────────
|
||||
|
||||
test_vm_stack() {
|
||||
local VM="$1"
|
||||
local STACK="$2"
|
||||
local LABEL="$VM/$STACK"
|
||||
local START_TIME=$(date +%s)
|
||||
|
||||
echo ""
|
||||
echo "── $LABEL ─────────────────────────────────────────────"
|
||||
|
||||
# Check VM running
|
||||
if ! v status "$VM" 2>/dev/null | grep -q "running"; then
|
||||
echo " SKIP: VM not running"
|
||||
echo " SKIP: VM not running (VM off)"
|
||||
return
|
||||
fi
|
||||
|
||||
# Find RPM
|
||||
local EL=$(vm_to_el "$VM")
|
||||
local RPM=$(ls -t $RPM_BASE/$EL/ja4ebpf-*.rpm | head -1)
|
||||
echo " RPM: $(basename $RPM)"
|
||||
|
||||
# Upload RPM
|
||||
if ! timeout "$UPLOAD_TIMEOUT" sh -c "cd $VM_DIR && vagrant upload $RPM /tmp/ja4ebpf-test.rpm $VM" 2>/dev/null; then
|
||||
echo " FAIL: RPM upload timeout"
|
||||
return
|
||||
fi
|
||||
|
||||
# Get VM IP
|
||||
local VM_IP=$(vssh "$VM" "ip -4 addr show eth0" | awk '/inet /{sub(/\/.*/,"",$2); print $2; exit}')
|
||||
if [ -z "$VM_IP" ]; then
|
||||
echo " SKIP: no eth0 IP"
|
||||
return
|
||||
fi
|
||||
echo " IP: $VM_IP"
|
||||
|
||||
# Generate and upload setup script
|
||||
local SETUP_SCRIPT=$(generate_setup_script "$STACK")
|
||||
if ! timeout "$UPLOAD_TIMEOUT" sh -c "cd $VM_DIR && vagrant upload $SETUP_SCRIPT /tmp/ja4-setup.sh $VM" 2>/dev/null; then
|
||||
echo " FAIL: setup script upload timeout"
|
||||
rm -f "$SETUP_SCRIPT"
|
||||
return
|
||||
fi
|
||||
rm -f "$SETUP_SCRIPT"
|
||||
|
||||
# Execute setup script on VM via SSH
|
||||
if ! timeout "$SSH_TIMEOUT" bash -c "cd '$VM_DIR' && vagrant ssh '$VM' -- 'sudo bash /tmp/ja4-setup.sh'" 2>/dev/null; then
|
||||
echo " FAIL: VM setup timeout/error"
|
||||
return
|
||||
fi
|
||||
|
||||
# [E] Generate traffic from HOST
|
||||
echo " [E] Traffic host→VM..."
|
||||
local HTTP_PORT HTTPS_PORT
|
||||
case "$STACK" in
|
||||
nginx) HTTP_PORT=80; HTTPS_PORT=443 ;;
|
||||
apache) HTTP_PORT=8080; HTTPS_PORT=8443 ;;
|
||||
hitch-varnish) HTTP_PORT=80; HTTPS_PORT=443 ;;
|
||||
esac
|
||||
|
||||
for i in $(seq 1 3); do
|
||||
curl -sf --max-time 5 "http://$VM_IP:$HTTP_PORT/health" -o /dev/null 2>&1 && echo " HTTP $i: OK" || echo " HTTP $i: FAIL"
|
||||
curl -skf --max-time 5 "https://$VM_IP:$HTTPS_PORT/health" -o /dev/null 2>&1 && echo " HTTPS $i: OK" || echo " HTTPS $i: FAIL"
|
||||
done
|
||||
|
||||
# [F] Wait for debug dump
|
||||
sleep 6
|
||||
|
||||
# [G] Collect results
|
||||
echo " [G] Results..."
|
||||
vssh "$VM" "sudo cat /tmp/ja4-test.log" > /tmp/vm-ja4-log.txt 2>/dev/null || true
|
||||
|
||||
# Cleanup VM
|
||||
timeout 15 bash -c "cd '$VM_DIR' && vagrant ssh '$VM' -- 'sudo pkill ja4ebpf 2>/dev/null; sudo nginx -s stop 2>/dev/null; sudo httpd -k stop 2>/dev/null; sudo pkill varnishd 2>/dev/null; sudo pkill hitch 2>/dev/null; sudo pkill -f python3 2>/dev/null; echo cleanup_done'" 2>/dev/null > /dev/null || true
|
||||
|
||||
# Parse on host side
|
||||
local LAST_DEBUG=$(grep "\[debug\] BPF:" /tmp/vm-ja4-log.txt 2>/dev/null | tail -1)
|
||||
local LAST_GO=$(grep "\[debug\] GO:" /tmp/vm-ja4-log.txt 2>/dev/null | tail -1)
|
||||
local SESSIONS=$(grep -c "session pr" /tmp/vm-ja4-log.txt 2>/dev/null || echo 0)
|
||||
local SYN_SUB=$(echo "$LAST_DEBUG" | sed -n 's/.*SYN_SUB=\([0-9]*\).*/\1/p')
|
||||
SYN_SUB=${SYN_SUB:-0}
|
||||
|
||||
local ELAPSED=$(( $(date +%s) - START_TIME ))
|
||||
echo " BPF: $LAST_DEBUG"
|
||||
echo " GO: $LAST_GO"
|
||||
echo " sessions=$SESSIONS SYN_SUB=$SYN_SUB (${ELAPSED}s)"
|
||||
|
||||
if [ "$SYN_SUB" -gt 0 ] 2>/dev/null; then
|
||||
echo " PASS: SYN_SUB=$SYN_SUB sessions=$SESSIONS"
|
||||
else
|
||||
echo " FAIL: SYN_SUB=0 (no TC capture)"
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Main loop ────────────────────────────────────────────────────────
|
||||
|
||||
for v in $VMS; do
|
||||
for s in $STACKS; do
|
||||
LABEL="$v/$s"
|
||||
TMPFILE=$(mktemp /tmp/ja4-test-XXXXXX.log)
|
||||
|
||||
# Run test_vm_stack in background, capture output
|
||||
(
|
||||
test_vm_stack "$v" "$s"
|
||||
) > "$TMPFILE" 2>&1 &
|
||||
TESTPID=$!
|
||||
|
||||
# Wait with timeout
|
||||
if ! timeout --signal=TERM --kill-after=10 "$TEST_TIMEOUT" bash -c "while kill -0 $TESTPID 2>/dev/null; do sleep 1; done"; then
|
||||
kill -9 $TESTPID 2>/dev/null || true
|
||||
wait $TESTPID 2>/dev/null || true
|
||||
echo ""
|
||||
echo "── $LABEL ─────────────────────────────────────────────"
|
||||
echo " TIMEOUT: exceeded ${TEST_TIMEOUT}s"
|
||||
else
|
||||
wait $TESTPID 2>/dev/null
|
||||
fi
|
||||
cat "$TMPFILE"
|
||||
|
||||
# Parse result from output
|
||||
if grep -q "^ PASS:" "$TMPFILE" 2>/dev/null; then
|
||||
PASS=$((PASS + 1)); RESULTS+=("PASS $LABEL")
|
||||
elif grep -q "^ FAIL:" "$TMPFILE" 2>/dev/null; then
|
||||
FAIL=$((FAIL + 1)); RESULTS+=("FAIL $LABEL")
|
||||
elif grep -q "^ SKIP:" "$TMPFILE" 2>/dev/null; then
|
||||
SKIP=$((SKIP + 1)); RESULTS+=("SKIP $LABEL")
|
||||
else
|
||||
FAIL=$((FAIL + 1)); RESULTS+=("FAIL $LABEL (no result)")
|
||||
fi
|
||||
rm -f "$TMPFILE"
|
||||
done
|
||||
done
|
||||
|
||||
# ── Summary ──────────────────────────────────────────────────────────
|
||||
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ SUMMARY: $PASS pass / $FAIL fail / $SKIP skip"
|
||||
echo "╚══════════════════════════════════════════════════════════════╝"
|
||||
for r in "${RESULTS[@]}"; do echo " $r"; done
|
||||
|
||||
[ "$FAIL" -eq 0 ] && exit 0 || exit 1
|
||||
Reference in New Issue
Block a user