feat: JA3 fingerprinting, SSL correlation fix, ML pipeline overhaul, E2E test infra
ja4ebpf: - Add JA3 raw + MD5 hash fingerprinting (ComputeJA3 in TLS parser) - Fix accept4 port double-swap bug (__builtin_bswap16 on already-host-order value) - Fix scheme override bug in ClickHouse writer (HTTP block clearing HTTPS) - Add HTTP/2 passive fingerprinting (Akamai H2 FP, SETTINGS, pseudo-header order) - Enrich ClickHouse schema with IP/TCP metadata, H2 settings, Sec-* headers - Ensure maximum data completeness: all available L3/L4, TLS, HTTP fields emitted bot-detector: - Replace logistic regression with MLP fusion classifier - Replace KS drift detection with ADWIN online learning - Replace NetworkX/Louvain with PyTorch Geometric GraphSAGE for fleet detection - Replace autoencoder with RealNVP normalizing flow + SessionTransformer embeddings infra: - Add distributed E2E test infrastructure (4 VMs: endpoints + analysis) - Add Vagrant provisioning for analysis VM, e2e Makefile targets, run scripts docs: - Restructure thesis into chapter files with corrected references - Add E2E testing documentation - Update architecture, schema, deployment, service docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -174,9 +174,6 @@ func main() {
|
|||||||
for s := range mgr.ReadyCh {
|
for s := range mgr.ReadyCh {
|
||||||
if w != nil {
|
if w != nil {
|
||||||
w.Write(s)
|
w.Write(s)
|
||||||
} else if cfg.Debug {
|
|
||||||
log.Printf("[ja4ebpf] DEBUG: session prête (sans CH): has_l3l4=%v has_tls=%v",
|
|
||||||
s.L3L4 != nil, s.TLS != nil)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -421,6 +418,7 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
|||||||
}
|
}
|
||||||
|
|
||||||
ja4 := parser.ComputeJA4(ch)
|
ja4 := parser.ComputeJA4(ch)
|
||||||
|
ja3Raw, ja3Hash := parser.ComputeJA3(ch)
|
||||||
|
|
||||||
var alpn []string
|
var alpn []string
|
||||||
var ciphers, extensions []uint16
|
var ciphers, extensions []uint16
|
||||||
@ -445,6 +443,8 @@ func consumeTLSEvents(ctx context.Context, rd *perf.Reader, mgr *correlation.Man
|
|||||||
s.TLS = &correlation.TLSInfo{
|
s.TLS = &correlation.TLSInfo{
|
||||||
ClientHelloRaw: payload,
|
ClientHelloRaw: payload,
|
||||||
JA4Hash: ja4,
|
JA4Hash: ja4,
|
||||||
|
JA3Raw: ja3Raw,
|
||||||
|
JA3Hash: ja3Hash,
|
||||||
SNI: ch.SNI,
|
SNI: ch.SNI,
|
||||||
ALPN: alpn,
|
ALPN: alpn,
|
||||||
CipherSuites: ciphers,
|
CipherSuites: ciphers,
|
||||||
|
|||||||
@ -31,6 +31,8 @@ type L3L4 struct {
|
|||||||
type TLSInfo struct {
|
type TLSInfo struct {
|
||||||
ClientHelloRaw []byte // payload ClientHello brut
|
ClientHelloRaw []byte // payload ClientHello brut
|
||||||
JA4Hash string // empreinte JA4 calculée
|
JA4Hash string // empreinte JA4 calculée
|
||||||
|
JA3Raw string // empreinte JA3 brute (format: version,ciphers,exts,groups,ecfmts)
|
||||||
|
JA3Hash string // empreinte JA3 hash MD5
|
||||||
SNI string // Server Name Indication
|
SNI string // Server Name Indication
|
||||||
ALPN []string // protocoles Application-Layer Protocol Negotiation
|
ALPN []string // protocoles Application-Layer Protocol Negotiation
|
||||||
TLSVersion uint16 // version TLS la plus haute annoncée
|
TLSVersion uint16 // version TLS la plus haute annoncée
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
@ -356,3 +357,81 @@ func ComputeJA4(ch *ClientHello) string {
|
|||||||
|
|
||||||
return part1 + "_" + part2 + "_" + part3
|
return part1 + "_" + part2 + "_" + part3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ComputeJA3 calcule l'empreinte JA3 selon la spécification Salesforce.
|
||||||
|
//
|
||||||
|
// Format : TLSVersion,CipherSuites,Extensions,EllipticCurves,EllipticPointFormats
|
||||||
|
// Chaque segment est une liste de valeurs décimales séparées par des tirets.
|
||||||
|
// Le JA3 hash est le MD5 hex de cette chaîne.
|
||||||
|
func ComputeJA3(ch *ClientHello) (ja3Raw string, ja3Hash string) {
|
||||||
|
// --- Version TLS ---
|
||||||
|
var tlsVer uint16
|
||||||
|
for _, v := range ch.SupportedVersions {
|
||||||
|
if !IsGREASE(v) && v > tlsVer {
|
||||||
|
tlsVer = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tlsVer == 0 {
|
||||||
|
tlsVer = ch.HandshakeVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Cipher suites (sans GREASE) ---
|
||||||
|
var ciphers []string
|
||||||
|
for _, cs := range ch.CipherSuites {
|
||||||
|
if !IsGREASE(cs) {
|
||||||
|
ciphers = append(ciphers, fmt.Sprintf("%d", cs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Extensions (sans GREASE, sans SNI 0x0000) ---
|
||||||
|
var exts []string
|
||||||
|
for _, e := range ch.Extensions {
|
||||||
|
if IsGREASE(e.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// SNI (0x0000) est inclus dans JA3
|
||||||
|
exts = append(exts, fmt.Sprintf("%d", e.Type))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Groupes elliptiques (extension supported_groups 0x000a) ---
|
||||||
|
var groups []string
|
||||||
|
for _, e := range ch.Extensions {
|
||||||
|
if e.Type == 0x000a && len(e.Data) >= 4 {
|
||||||
|
// Format : longueur (2 octets) + liste de groupes (2 octets chacun)
|
||||||
|
groupLen := int(binary.BigEndian.Uint16(e.Data[:2]))
|
||||||
|
for i := 2; i+1 < len(e.Data) && i < groupLen+2; i += 2 {
|
||||||
|
g := binary.BigEndian.Uint16(e.Data[i : i+2])
|
||||||
|
if !IsGREASE(g) {
|
||||||
|
groups = append(groups, fmt.Sprintf("%d", g))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Formats de points elliptiques (extension ec_point_formats 0x000b) ---
|
||||||
|
var ecPointFormats []string
|
||||||
|
for _, e := range ch.Extensions {
|
||||||
|
if e.Type == 0x000b && len(e.Data) >= 2 {
|
||||||
|
fmtLen := int(e.Data[0])
|
||||||
|
for i := 1; i < len(e.Data) && i <= fmtLen; i++ {
|
||||||
|
ecPointFormats = append(ecPointFormats, fmt.Sprintf("%d", e.Data[i]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Assemblage JA3 raw ---
|
||||||
|
parts := []string{
|
||||||
|
fmt.Sprintf("%d", tlsVer),
|
||||||
|
strings.Join(ciphers, "-"),
|
||||||
|
strings.Join(exts, "-"),
|
||||||
|
strings.Join(groups, "-"),
|
||||||
|
strings.Join(ecPointFormats, "-"),
|
||||||
|
}
|
||||||
|
ja3Raw = strings.Join(parts, ",")
|
||||||
|
|
||||||
|
// --- JA3 hash = MD5 du raw ---
|
||||||
|
md5Hash := md5.Sum([]byte(ja3Raw))
|
||||||
|
ja3Hash = hex.EncodeToString(md5Hash[:])
|
||||||
|
|
||||||
|
return ja3Raw, ja3Hash
|
||||||
|
}
|
||||||
|
|||||||
@ -47,6 +47,8 @@ type sessionRecord struct {
|
|||||||
|
|
||||||
// TLS (noms attendus par le MV)
|
// TLS (noms attendus par le MV)
|
||||||
JA4Hash string `json:"ja4,omitempty"`
|
JA4Hash string `json:"ja4,omitempty"`
|
||||||
|
JA3Raw string `json:"ja3,omitempty"`
|
||||||
|
JA3Hash string `json:"ja3_hash,omitempty"`
|
||||||
TLSSNI string `json:"tls_sni,omitempty"`
|
TLSSNI string `json:"tls_sni,omitempty"`
|
||||||
TLSALPN string `json:"tls_alpn,omitempty"`
|
TLSALPN string `json:"tls_alpn,omitempty"`
|
||||||
TLSVersion string `json:"tls_version,omitempty"`
|
TLSVersion string `json:"tls_version,omitempty"`
|
||||||
@ -242,6 +244,8 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
|
|||||||
// Champs TLS
|
// Champs TLS
|
||||||
if s.TLS != nil {
|
if s.TLS != nil {
|
||||||
rec.JA4Hash = s.TLS.JA4Hash
|
rec.JA4Hash = s.TLS.JA4Hash
|
||||||
|
rec.JA3Raw = s.TLS.JA3Raw
|
||||||
|
rec.JA3Hash = s.TLS.JA3Hash
|
||||||
rec.TLSSNI = s.TLS.SNI
|
rec.TLSSNI = s.TLS.SNI
|
||||||
rec.TLSALPN = strings.Join(s.TLS.ALPN, ",")
|
rec.TLSALPN = strings.Join(s.TLS.ALPN, ",")
|
||||||
rec.TLSVersion = formatTLSVersion(s.TLS.TLSVersion)
|
rec.TLSVersion = formatTLSVersion(s.TLS.TLSVersion)
|
||||||
@ -262,7 +266,9 @@ func sessionToRecord(s *correlation.SessionState) sessionRecord {
|
|||||||
rec.Path = last.Path
|
rec.Path = last.Path
|
||||||
rec.QueryString = last.QueryString
|
rec.QueryString = last.QueryString
|
||||||
rec.Host = last.Host
|
rec.Host = last.Host
|
||||||
rec.Scheme = "" // sera rempli par le dispatcher si TLS
|
if last.Host != "" && s.TLS != nil {
|
||||||
|
rec.Scheme = "https"
|
||||||
|
}
|
||||||
rec.HTTPVersion = last.HTTPVersion
|
rec.HTTPVersion = last.HTTPVersion
|
||||||
rec.StatusCode = &last.StatusCode
|
rec.StatusCode = &last.StatusCode
|
||||||
rec.ResponseSize = &last.ResponseSize
|
rec.ResponseSize = &last.ResponseSize
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
# run-e2e-test.sh — Test E2E distribué ja4-platform
|
# run-e2e-test.sh — Test E2E distribué ja4-platform
|
||||||
#
|
#
|
||||||
# Architecture :
|
# Architecture :
|
||||||
# 3 VMs endpoint (centos8/rocky9/rocky10) : nginx + ja4ebpf
|
# 3 VMs endpoint (centos8 / rocky9 / rocky10) : nginx + ja4ebpf
|
||||||
# 1 VM analysis (192.168.42.10) : ClickHouse + bot-detector + dashboard
|
# 1 VM analysis (192.168.42.10) : ClickHouse + bot-detector + dashboard
|
||||||
# Host : orchestrateur + génération de trafic
|
# Host : orchestrateur + génération de trafic
|
||||||
#
|
#
|
||||||
@ -31,8 +31,8 @@ set -euo pipefail
|
|||||||
VM_DIR="$(cd "$(dirname "$0")" && pwd)"
|
VM_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
PROJECT_ROOT="$(cd "$VM_DIR/../.." && pwd)"
|
PROJECT_ROOT="$(cd "$VM_DIR/../.." && pwd)"
|
||||||
ANALYSIS_IP="192.168.42.10"
|
ANALYSIS_IP="192.168.42.10"
|
||||||
ENDPOINT_VMS="rocky9 rocky10"
|
ENDPOINT_VMS="centos8 rocky9 rocky10"
|
||||||
ALL_VMS="rocky9 rocky10 analysis"
|
ALL_VMS="centos8 rocky9 rocky10 analysis"
|
||||||
STACK="nginx"
|
STACK="nginx"
|
||||||
|
|
||||||
# ── Paramètres par défaut (surchARGEABLES par CLI ou env) ──
|
# ── Paramètres par défaut (surchARGEABLES par CLI ou env) ──
|
||||||
@ -94,6 +94,7 @@ for v in $(echo "$TLS_VERSIONS" | tr ',' ' '); do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
[ -z "$CURL_TLS_FLAGS" ] && CURL_TLS_FLAGS="--tlsv1.2 --tlsv1.3" && TLS_VERSIONS="1.2,1.3"
|
[ -z "$CURL_TLS_FLAGS" ] && CURL_TLS_FLAGS="--tlsv1.2 --tlsv1.3" && TLS_VERSIONS="1.2,1.3"
|
||||||
|
CURL_TLS_FLAGS="${CURL_TLS_FLAGS# }" # trim leading space
|
||||||
|
|
||||||
# Nombre de requêtes HTTP vs HTTPS dérivés du ratio
|
# Nombre de requêtes HTTP vs HTTPS dérivés du ratio
|
||||||
HTTPS_COUNT=$(python3 -c "print(int(${TRAFFIC_COUNT} * (1 - ${HTTP_RATIO})))")
|
HTTPS_COUNT=$(python3 -c "print(int(${TRAFFIC_COUNT} * (1 - ${HTTP_RATIO})))")
|
||||||
@ -292,31 +293,11 @@ phase2_endpoints() {
|
|||||||
phase3_traffic() {
|
phase3_traffic() {
|
||||||
echo ""
|
echo ""
|
||||||
echo "╔══════════════════════════════════════════════════════════╗"
|
echo "╔══════════════════════════════════════════════════════════╗"
|
||||||
echo "║ Phase 3 : Génération de trafic host → endpoints ║"
|
echo "║ Phase 3 : Génération de trafic → endpoints ║"
|
||||||
echo "╚══════════════════════════════════════════════════════════╝"
|
echo "╚══════════════════════════════════════════════════════════╝"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
local total_ok=0 total_err=0
|
local total_ok=0
|
||||||
|
|
||||||
# User-Agents variés pour diversifier les empreintes TLS/HTTP
|
|
||||||
local UA_BROWSER=( \
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36" \
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15" \
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0" \
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0" \
|
|
||||||
)
|
|
||||||
local UA_BOT=( \
|
|
||||||
"python-requests/2.32.3" \
|
|
||||||
"curl/8.9.1" \
|
|
||||||
"Go-http-client/2.0" \
|
|
||||||
"python-httpx/0.28.1" \
|
|
||||||
"Googlebot/2.1" \
|
|
||||||
)
|
|
||||||
|
|
||||||
# Paths diversifiés pour des fingerprints différents
|
|
||||||
local PATHS=("/" "/health" "/data" "/api/users" "/api/v1/status" "/api/v1/metrics" \
|
|
||||||
"/login" "/logout" "/api/search" "/static/main.js" "/static/style.css" \
|
|
||||||
"/favicon.ico" "/robots.txt" "/sitemap.xml" "/api/v2/data" "/admin")
|
|
||||||
|
|
||||||
# Hostnames pour SNI (cert CN=platform.test, nginx accepte tout via server_name _)
|
# Hostnames pour SNI (cert CN=platform.test, nginx accepte tout via server_name _)
|
||||||
local SNI_HOSTS_ALL=("platform.test" "api.platform.test" "www.platform.test" "app.platform.test")
|
local SNI_HOSTS_ALL=("platform.test" "api.platform.test" "www.platform.test" "app.platform.test")
|
||||||
@ -347,133 +328,90 @@ phase3_traffic() {
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Trafic HTTPS généré depuis les VMs (IPs sources distinctes) ──
|
# ── Construire les listes d'IPs cibles et SNI ──
|
||||||
for src_vm in $ENDPOINT_VMS; do
|
local TARGET_IPS=""
|
||||||
local src_ip="${VM_IPS_MAP[$src_vm]}"
|
for target_vm in $ENDPOINT_VMS; do
|
||||||
log "Génération depuis $src_vm ($src_ip) : ${HTTPS_COUNT} requêtes HTTPS (${SRC_IP_COUNT} IPs src)..."
|
TARGET_IPS="$TARGET_IPS ${VM_IPS_MAP[$target_vm]}"
|
||||||
|
done
|
||||||
|
TARGET_IPS=$(echo $TARGET_IPS)
|
||||||
|
|
||||||
# Construire la liste des IPs cibles (toutes les endpoints)
|
local SNI_HOSTS_STR=""
|
||||||
local TARGET_IPS=""
|
for h in "${SNI_HOSTS[@]}"; do
|
||||||
for target_vm in $ENDPOINT_VMS; do
|
SNI_HOSTS_STR="$SNI_HOSTS_STR $h"
|
||||||
TARGET_IPS="$TARGET_IPS ${VM_IPS_MAP[$target_vm]}"
|
done
|
||||||
|
SNI_HOSTS_STR=$(echo $SNI_HOSTS_STR)
|
||||||
|
|
||||||
|
# ── Synchroniser generate-traffic.sh vers les VMs ──
|
||||||
|
log "Synchronisation du script de trafic..."
|
||||||
|
for vm in $ENDPOINT_VMS; do
|
||||||
|
vagrant rsync "$vm" 2>&1 | tail -1
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Écrire le config et lancer le trafic depuis chaque VM ──
|
||||||
|
for src_vm in $ENDPOINT_VMS; do
|
||||||
|
log "Génération depuis $src_vm : ${HTTPS_COUNT} HTTPS + ${HTTP_COUNT} HTTP (${SRC_IP_COUNT} IPs src)..."
|
||||||
|
|
||||||
|
# Écrire le fichier de config sur la VM (heredoc quoté — pas d'expansion SSH)
|
||||||
|
vagrant ssh "$src_vm" -- "cat > /tmp/e2e-traffic.env << 'ENVEOF'
|
||||||
|
export HITS=${HTTPS_COUNT}
|
||||||
|
export HITS_HTTP=${HTTP_COUNT}
|
||||||
|
export TARGET_IPS='${TARGET_IPS}'
|
||||||
|
export SNI_HOSTS='${SNI_HOSTS_STR}'
|
||||||
|
export TLS_FLAGS='${CURL_TLS_FLAGS}'
|
||||||
|
export SRC_IP_COUNT=${SRC_IP_COUNT}
|
||||||
|
ENVEOF"
|
||||||
|
|
||||||
|
# Lancer le générateur de trafic en arrière-plan
|
||||||
|
vagrant ssh "$src_vm" -- \
|
||||||
|
"source /tmp/e2e-traffic.env && bash /ja4-platform/tests/vm/generate-traffic.sh" \
|
||||||
|
> /tmp/e2e-traffic-${src_vm}.out 2>&1 &
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── Collecter les résultats HTTPS + HTTP ──
|
||||||
|
for src_vm in $ENDPOINT_VMS; do
|
||||||
|
# Attendre que le processus se termine (max 300s)
|
||||||
|
for i in $(seq 1 300); do
|
||||||
|
if [ -f /tmp/e2e-traffic-${src_vm}.out ] && ! pgrep -f "vagrant ssh $src_vm.*generate-traffic" >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
done
|
done
|
||||||
|
|
||||||
# Script de génération exécuté sur la VM source
|
|
||||||
vagrant ssh "$src_vm" -- "bash -s" <<REMOTE_SCRIPT &
|
|
||||||
#!/bin/bash
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
HITS=${HTTPS_COUNT}
|
|
||||||
TARGET_IPS=(${TARGET_IPS})
|
|
||||||
SNI_HOSTS=(${SNI_HOSTS[@]})
|
|
||||||
TLS_FLAGS="${CURL_TLS_FLAGS}"
|
|
||||||
DNS_COUNT=${DNS_COUNT}
|
|
||||||
SRC_IP_COUNT=${SRC_IP_COUNT}
|
|
||||||
|
|
||||||
# Collecter les IPs sources disponibles sur eth0
|
|
||||||
SRC_IPS=(\$(ip -4 addr show eth0 2>/dev/null | awk '/inet / {sub(/\/.*/, "", \$2); print \$2}'))
|
|
||||||
|
|
||||||
UA_BROWSER=(
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36"
|
|
||||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15"
|
|
||||||
"Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0"
|
|
||||||
)
|
|
||||||
UA_BOT=(
|
|
||||||
"python-requests/2.32.3"
|
|
||||||
"curl/8.9.1"
|
|
||||||
"Go-http-client/2.0"
|
|
||||||
"python-httpx/0.28.1"
|
|
||||||
"Googlebot/2.1"
|
|
||||||
)
|
|
||||||
PATHS=("/" "/health" "/data" "/api/users" "/api/v1/status" "/api/v1/metrics" \
|
|
||||||
"/login" "/logout" "/api/search" "/static/main.js" "/static/style.css" \
|
|
||||||
"/favicon.ico" "/robots.txt" "/sitemap.xml" "/api/v2/data" "/admin")
|
|
||||||
|
|
||||||
ok=0
|
|
||||||
for i in \$(seq 1 \$HITS); do
|
|
||||||
idx=\$((i - 1))
|
|
||||||
target_ip="\${TARGET_IPS[\$((idx % \${#TARGET_IPS[@]}))]}"
|
|
||||||
sni_host="\${SNI_HOSTS[\$((idx % \${#SNI_HOSTS[@]}))]}"
|
|
||||||
path="\${PATHS[\$((idx % \${#PATHS[@]}))]}"
|
|
||||||
|
|
||||||
case \$((i % 10)) in
|
|
||||||
0|1|2|3|4) method="GET" ;;
|
|
||||||
5|6) method="POST" ;;
|
|
||||||
7) method="PUT" ;;
|
|
||||||
8) method="DELETE" ;;
|
|
||||||
9) method="HEAD" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ \$((i % 10)) -lt 7 ]; then
|
|
||||||
ua="\${UA_BROWSER[\$((idx % \${#UA_BROWSER[@]}))]}"
|
|
||||||
else
|
|
||||||
ua="\${UA_BOT[\$((idx % \${#UA_BOT[@]}))]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
extra_flags="--resolve \${sni_host}:443:\${target_ip} \$TLS_FLAGS"
|
|
||||||
|
|
||||||
# Alterner entre les IPs sources disponibles
|
|
||||||
if [ \${#SRC_IPS[@]} -gt 1 ]; then
|
|
||||||
src_ip="\${SRC_IPS[\$((idx % \${#SRC_IPS[@]}))]}"
|
|
||||||
extra_flags="\$extra_flags --interface \$src_ip"
|
|
||||||
fi
|
|
||||||
|
|
||||||
case \$method in
|
|
||||||
POST)
|
|
||||||
curl -sf -k \$extra_flags -X POST "https://\${sni_host}\${path}" \
|
|
||||||
-H "User-Agent: \${ua}" -H "Content-Type: application/json" \
|
|
||||||
-d '{"test":1,"seq":'\$i'}' >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
|
|
||||||
PUT)
|
|
||||||
curl -sf -k \$extra_flags -X PUT "https://\${sni_host}\${path}" \
|
|
||||||
-H "User-Agent: \${ua}" -d '{}' >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
|
|
||||||
DELETE)
|
|
||||||
curl -sf -k \$extra_flags -X DELETE "https://\${sni_host}\${path}" \
|
|
||||||
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
|
|
||||||
HEAD)
|
|
||||||
curl -sf -k \$extra_flags -I "https://\${sni_host}\${path}" \
|
|
||||||
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
|
|
||||||
*)
|
|
||||||
curl -sf -k \$extra_flags -X "\$method" "https://\${sni_host}\${path}" \
|
|
||||||
-H "User-Agent: \${ua}" >/dev/null 2>&1 && ok=\$((ok + 1)) || true ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
echo "\$ok/\$HITS"
|
|
||||||
REMOTE_SCRIPT
|
|
||||||
done
|
|
||||||
|
|
||||||
# Collecter les résultats des processus en arrière-plan
|
|
||||||
for src_vm in $ENDPOINT_VMS; do
|
|
||||||
local result
|
local result
|
||||||
result=$(wait 2>/dev/null || echo "?/?")
|
result=$(tail -1 /tmp/e2e-traffic-${src_vm}.out 2>/dev/null || echo "0/${HTTPS_COUNT} 0/${HTTP_COUNT}")
|
||||||
log " $src_vm HTTPS : $result requêtes réussies"
|
rm -f /tmp/e2e-traffic-${src_vm}.out 2>/dev/null
|
||||||
local ok_count
|
|
||||||
ok_count=$(echo "$result" | cut -d/ -f1)
|
# Format: "ok_https/hits_https ok_http/hits_http"
|
||||||
total_ok=$((total_ok + ok_count))
|
local https_result http_result
|
||||||
|
https_result=$(echo "$result" | awk '{print $1}')
|
||||||
|
http_result=$(echo "$result" | awk '{print $2}')
|
||||||
|
local ok_https ok_http
|
||||||
|
ok_https=$(echo "$https_result" | cut -d/ -f1)
|
||||||
|
ok_http=$(echo "${http_result:-0/0}" | cut -d/ -f1)
|
||||||
|
|
||||||
|
log " $src_vm HTTPS : ${https_result} HTTP : ${http_result:-0/0}"
|
||||||
|
total_ok=$((total_ok + ok_https + ok_http))
|
||||||
done
|
done
|
||||||
|
|
||||||
# HTTP/2 massif depuis les VMs si httpx est disponible
|
# ── HTTP/2 massif depuis les VMs si httpx est disponible ──
|
||||||
for src_vm in $ENDPOINT_VMS; do
|
for src_vm in $ENDPOINT_VMS; do
|
||||||
if vagrant ssh "$src_vm" -- 'python3 -c "import httpx"' 2>/dev/null; then
|
if vagrant ssh "$src_vm" -- 'python3 -c "import httpx"' 2>/dev/null; then
|
||||||
local src_ip="${VM_IPS_MAP[$src_vm]}"
|
local src_ip="${VM_IPS_MAP[$src_vm]}"
|
||||||
log "Génération HTTP/2 depuis $src_vm (${HTTPS_COUNT} requêtes, TLS=${TLS_VERSIONS}, DNS=${DNS_COUNT})..."
|
log "Génération HTTP/2 depuis $src_vm (${HTTPS_COUNT} requêtes, TLS=${TLS_VERSIONS}, DNS=${DNS_COUNT})..."
|
||||||
local TARGET_IPS_H2=""
|
|
||||||
for target_vm in $ENDPOINT_VMS; do
|
# Écrire le script httpx sur la VM (évite les problèmes d'échappement)
|
||||||
TARGET_IPS_H2="$TARGET_IPS_H2 ${VM_IPS_MAP[$target_vm]}"
|
vagrant ssh "$src_vm" -- "cat > /tmp/e2e-h2-traffic.py << 'PYEOF'
|
||||||
done
|
import httpx, ssl as _ssl, warnings, random, os
|
||||||
vagrant ssh "$src_vm" -- "python3 -c \"
|
|
||||||
import httpx, ssl as _ssl, warnings, random
|
|
||||||
warnings.filterwarnings('ignore')
|
warnings.filterwarnings('ignore')
|
||||||
paths = ['/', '/health', '/data', '/api/users', '/api/v1/status', '/login', '/api/search']
|
paths = ['/', '/health', '/data', '/api/users', '/api/v1/status', '/login', '/api/search']
|
||||||
sni_hosts = ${SNI_HOSTS[@]@Q}
|
sni_hosts = os.environ.get('SNI_HOSTS', 'platform.test').split()
|
||||||
target_ips = '${TARGET_IPS_H2}'.split()
|
target_ips = os.environ.get('TARGET_IPS', '127.0.0.1').split()
|
||||||
|
tls_versions = [v.strip() for v in os.environ.get('TLS_VERSIONS', '1.2,1.3').split(',')]
|
||||||
uas_browser = [
|
uas_browser = [
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/131.0.0.0 Safari/537.36',
|
||||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 Safari/605.1.15',
|
||||||
'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0',
|
'Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0',
|
||||||
]
|
]
|
||||||
uas_bot = ['python-httpx/0.28.1', 'Googlebot/2.1', 'Go-http-client/2.0']
|
uas_bot = ['python-httpx/0.28.1', 'Googlebot/2.1', 'Go-http-client/2.0']
|
||||||
tls_versions = [v.strip() for v in '${TLS_VERSIONS}'.split(',')]
|
|
||||||
supported = {'1.2': _ssl.TLSVersion.TLSv1_2, '1.3': _ssl.TLSVersion.TLSv1_3}
|
supported = {'1.2': _ssl.TLSVersion.TLSv1_2, '1.3': _ssl.TLSVersion.TLSv1_3}
|
||||||
tls_map = [supported[v] for v in tls_versions if v in supported]
|
tls_map = [supported[v] for v in tls_versions if v in supported]
|
||||||
ctx = _ssl.SSLContext(_ssl.PROTOCOL_TLS_CLIENT)
|
ctx = _ssl.SSLContext(_ssl.PROTOCOL_TLS_CLIENT)
|
||||||
@ -482,8 +420,9 @@ ctx.verify_mode = _ssl.CERT_NONE
|
|||||||
if tls_map:
|
if tls_map:
|
||||||
ctx.minimum_version = min(tls_map)
|
ctx.minimum_version = min(tls_map)
|
||||||
ctx.maximum_version = max(tls_map)
|
ctx.maximum_version = max(tls_map)
|
||||||
|
hits = int(os.environ.get('HITS', '100'))
|
||||||
with httpx.Client(http2=True, verify=ctx) as c:
|
with httpx.Client(http2=True, verify=ctx) as c:
|
||||||
for i in range(${HTTPS_COUNT}):
|
for i in range(hits):
|
||||||
p = random.choice(paths)
|
p = random.choice(paths)
|
||||||
target = random.choice(target_ips)
|
target = random.choice(target_ips)
|
||||||
h = random.choice(sni_hosts)
|
h = random.choice(sni_hosts)
|
||||||
@ -492,30 +431,15 @@ with httpx.Client(http2=True, verify=ctx) as c:
|
|||||||
c.get(f'https://{target}' + p, headers={'User-Agent': ua, 'Host': h})
|
c.get(f'https://{target}' + p, headers={'User-Agent': ua, 'Host': h})
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
\"" 2>/dev/null || true
|
PYEOF"
|
||||||
|
|
||||||
|
vagrant ssh "$src_vm" -- \
|
||||||
|
"source /tmp/e2e-traffic.env && TLS_VERSIONS='${TLS_VERSIONS}' python3 /tmp/e2e-h2-traffic.py" \
|
||||||
|
2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
pass "HTTP/2 généré depuis tous les endpoints"
|
pass "HTTP/2 généré depuis tous les endpoints"
|
||||||
|
|
||||||
# Trafic HTTP (port 80) en plus pour diversifier
|
|
||||||
if [ "${HTTP_COUNT}" -gt 0 ]; then
|
|
||||||
log "Génération HTTP (port 80) depuis les VMs : ${HTTP_COUNT} requêtes/VM..."
|
|
||||||
for src_vm in $ENDPOINT_VMS; do
|
|
||||||
local ok80
|
|
||||||
ok80=$(vagrant ssh "$src_vm" -- "
|
|
||||||
ok=0
|
|
||||||
for i in \$(seq 1 ${HTTP_COUNT}); do
|
|
||||||
curl -sf http://localhost/health >/dev/null 2>&1 && ok=\$((ok + 1)) || true
|
|
||||||
done
|
|
||||||
echo \$ok
|
|
||||||
" 2>/dev/null || echo "0")
|
|
||||||
log " $src_vm HTTP : ${ok80}/${HTTP_COUNT} requêtes"
|
|
||||||
total_ok=$((total_ok + ok80))
|
|
||||||
done
|
|
||||||
else
|
|
||||||
log "HTTP (port 80) désactivé (http-ratio=0)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
pass "Trafic total : ${total_ok} requêtes réussies"
|
pass "Trafic total : ${total_ok} requêtes réussies"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user