feat(e2e): add distributed E2E test framework with parametric traffic generation

Add run-e2e-test.sh with CLI parameters (--hits, --http-ratio, --dns, --tls,
--src-ips, --keep-analysis, --up) for configurable traffic generation. Traffic
runs from VM endpoints with multiple source IPs (alias IPs on eth0) to produce
distinct sessions for the ML pipeline. Fix curl TLS flags (--tlsv1.2 instead
of --tls-v1-2), skip redundant local verification in distributed mode, and
fix dashboard is_available() cache that never retried after ClickHouse recovery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-04-15 00:09:32 +02:00
parent 7894d39f1c
commit f88b739992
40 changed files with 2154 additions and 337 deletions

View File

@ -4,15 +4,28 @@ from __future__ import annotations
import logging
from fastapi import FastAPI
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from backend.database import ClickHouseUnavailable, is_available
from backend.routes.api import router as api_router
from backend.routes.pages import router as pages_router
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
_templates = Jinja2Templates(directory="backend/templates")
_PAGE_MAP = {
"/": "overview", "/detections": "detections", "/scores": "scores",
"/traffic": "traffic", "/classify": "classify", "/features": "features",
"/models": "models", "/network": "network", "/campaigns": "campaigns",
"/tactics": "tactics", "/reflists": "reflists", "/fleet": "fleet",
"/health": "health", "/browsers": "browsers", "/fingerprints": "fingerprints",
}
app = FastAPI(title="JA4 SOC Dashboard", version="1.0.0")
# CORS — allow all origins for dashboard access
@ -24,6 +37,29 @@ app.add_middleware(
allow_headers=["*"],
)
@app.exception_handler(ClickHouseUnavailable)
async def ch_unavailable_handler(request: Request, exc: ClickHouseUnavailable):
"""Return 503 for API calls, render degraded pages for HTML requests."""
accept = request.headers.get("accept", "")
path = request.url.path
# If the client expects JSON (API call), return 503 JSON
if "application/json" in accept or path.startswith("/api/"):
return JSONResponse(
status_code=503,
content={"detail": "ClickHouse unavailable", "error": str(exc)},
)
# For HTML pages, render the template with ch_available=False
page_name = _PAGE_MAP.get(path, "overview")
return _templates.TemplateResponse(
f"{page_name}.html",
{"request": request, "active_page": page_name, "ch_available": False},
status_code=503,
)
# Static assets
app.mount("/static", StaticFiles(directory="backend/static"), name="static")
@ -32,6 +68,7 @@ app.include_router(api_router)
app.include_router(pages_router)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/api/healthcheck")
async def healthcheck():
ch = is_available()
return {"status": "ok" if ch else "degraded", "clickhouse": "up" if ch else "down"}