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>
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""JA4 SOC Dashboard — FastAPI application."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
|
|
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
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
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")
|
|
|
|
# Routers — API first so /api/* paths match before page catch-all
|
|
app.include_router(api_router)
|
|
app.include_router(pages_router)
|
|
|
|
|
|
@app.get("/api/healthcheck")
|
|
async def healthcheck():
|
|
ch = is_available()
|
|
return {"status": "ok" if ch else "degraded", "clickhouse": "up" if ch else "down"}
|