Architecture: - ja4_logs: raw log ingestion (http_logs_raw, http_logs, mv_http_logs) - ja4_processing: analytics, aggregation, ML, dictionaries, audit Configuration (env vars): - CLICKHOUSE_DB_LOGS (default: ja4_logs) - CLICKHOUSE_DB_PROCESSING (default: ja4_processing) Changes: - SQL migrations (10 files): all mabase_prod refs → ja4_logs or ja4_processing with correct cross-database references (MVs, views, dicts) - deploy_schema.sh: substitutes DB names from env vars at deploy time - Python shared settings: added CLICKHOUSE_DB_LOGS + CLICKHOUSE_DB_PROCESSING - Dashboard routes (19 files): replaced ~80 hardcoded mabase_prod refs with settings.CLICKHOUSE_DB_LOGS / settings.CLICKHOUSE_DB_PROCESSING - Bot-detector: DB → CLICKHOUSE_DB_PROCESSING, fetch_rules.py configurable - Correlator: DSN example updated to ja4_logs - Docker-compose + .env files: new env vars with defaults - All documentation updated (14 markdown files) All tests pass: sentinel 10/10, correlator 67.1%, bot-detector 11, dashboard 20, ja4_common 18 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
238 lines
8.5 KiB
Python
238 lines
8.5 KiB
Python
"""
|
||
Bot Detector Dashboard - API Backend
|
||
FastAPI application pour servir le dashboard web
|
||
"""
|
||
import logging
|
||
from contextlib import asynccontextmanager
|
||
from fastapi import FastAPI, HTTPException
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from fastapi.staticfiles import StaticFiles
|
||
from fastapi.responses import FileResponse
|
||
import os
|
||
|
||
from .config import settings
|
||
from .database import db
|
||
from .routes import metrics, detections, variability, attributes, analysis, entities, incidents, audit, reputation, fingerprints
|
||
from .routes import bruteforce, tcp_spoofing, header_fingerprint, heatmap, botnets, rotation, ml_features, investigation_summary, search, clustering
|
||
|
||
# Configuration logging
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""Gestion du cycle de vie de l'application"""
|
||
# Startup
|
||
logger.info("Démarrage du Bot Detector Dashboard API...")
|
||
logger.info(f"ClickHouse: {settings.CLICKHOUSE_HOST}:{settings.CLICKHOUSE_PORT}")
|
||
logger.info(f"Database: {settings.CLICKHOUSE_DB}")
|
||
|
||
# Tester la connexion ClickHouse
|
||
try:
|
||
client = db.connect()
|
||
client.ping()
|
||
logger.info("Connexion ClickHouse établie avec succès")
|
||
except Exception as e:
|
||
logger.error(f"Échec de connexion ClickHouse: {e}")
|
||
raise
|
||
|
||
yield
|
||
|
||
# Shutdown
|
||
logger.info("Arrêt du Bot Detector Dashboard API...")
|
||
db.close()
|
||
|
||
|
||
# Création de l'application FastAPI
|
||
OPENAPI_TAGS = [
|
||
{
|
||
"name": "Metrics",
|
||
"description": "Métriques globales : comptages, niveaux de menace, baseline et distribution des scores ML.",
|
||
},
|
||
{
|
||
"name": "Detections",
|
||
"description": "Liste paginée et filtrée des anomalies détectées par le modèle ML. Supporte tri, recherche texte et regroupement par IP.",
|
||
},
|
||
{
|
||
"name": "investigation",
|
||
"description": (
|
||
"**Point d'entrée principal pour l'analyse d'une IP.** "
|
||
"Agrège en un seul appel : score ML, brute-force, spoofing TCP, rotation JA4, persistance et timeline 24h. "
|
||
"Retourne un `risk_score` heuristique de 0 à 100."
|
||
),
|
||
},
|
||
{
|
||
"name": "Reputation",
|
||
"description": "Réputation externe d'une IP via IP-API.com et IPinfo.io (sans clé API). Détecte proxies, VPN, Tor, hébergeurs.",
|
||
},
|
||
{
|
||
"name": "Analysis",
|
||
"description": "Analyses approfondies par IP : subnet, pays, empreintes JA4, user-agents, recommandation SOC et gestion des classifications.",
|
||
},
|
||
{
|
||
"name": "Entities",
|
||
"description": "Investigation par entité (IP, JA4, subnet, user-agent, host). Retourne détections associées, user-agents, chemins, paramètres et entités liées.",
|
||
},
|
||
{
|
||
"name": "Incidents",
|
||
"description": "Clusters d'incidents actifs regroupés par similarité comportementale. Permet la classification et le suivi des incidents.",
|
||
},
|
||
{
|
||
"name": "Fingerprints",
|
||
"description": "Analyse des empreintes JA4/TLS : spoofing, matrice JA4↔UA, user-agents suspects, cohérence par IP, JA4 légitimes et corrélation ASN.",
|
||
},
|
||
{
|
||
"name": "Bruteforce",
|
||
"description": "Détection des attaques brute-force : cibles, attaquants, timeline et détail par host.",
|
||
},
|
||
{
|
||
"name": "TCP Spoofing",
|
||
"description": "Détection du spoofing TCP/OS fingerprinting : vue d'ensemble, liste et matrice TTL×MSS.",
|
||
},
|
||
{
|
||
"name": "Header Fingerprint",
|
||
"description": "Clusters de fingerprints d'en-têtes HTTP suspects et IPs associées.",
|
||
},
|
||
{
|
||
"name": "Heatmap",
|
||
"description": "Heatmap horaire du trafic, top hosts et matrice activité/heure.",
|
||
},
|
||
{
|
||
"name": "Botnets",
|
||
"description": "Détection de botnets : spread JA4, distribution géographique par JA4, résumé global.",
|
||
},
|
||
{
|
||
"name": "Rotation",
|
||
"description": "Détection de la rotation JA4 (évasion de détection), menaces persistantes, historique JA4 par IP et score de sophistication.",
|
||
},
|
||
{
|
||
"name": "ML Features",
|
||
"description": "Données brutes du modèle ML : top anomalies, radar par IP, distribution des scores, tendances, features B et scatter plot.",
|
||
},
|
||
{
|
||
"name": "Attributes",
|
||
"description": "Listes des valeurs distinctes d'attributs (JA4, user-agents, ASN, pays…) avec comptages.",
|
||
},
|
||
{
|
||
"name": "Variability",
|
||
"description": "Variabilité comportementale : IPs par attribut, attributs par valeur, analyse des user-agents.",
|
||
},
|
||
{
|
||
"name": "Clustering",
|
||
"description": "Clustering K-Means des IPs sur les features ML. Statut du cache, clusters, points et IPs par cluster.",
|
||
},
|
||
{
|
||
"name": "Search",
|
||
"description": "Recherche rapide cross-entités (IP, JA4, host, user-agent, pays, ASN).",
|
||
},
|
||
{
|
||
"name": "Audit",
|
||
"description": "Journal d'audit SOC : création de logs, consultation filtrée, statistiques et activité par utilisateur.",
|
||
},
|
||
]
|
||
|
||
app = FastAPI(
|
||
title="Bot Detector Dashboard API",
|
||
description=(
|
||
"API REST du **Bot Detector SOC Dashboard**.\n\n"
|
||
"Permet d'interroger les bases ClickHouse (`ja4_logs` / `ja4_processing`) pour visualiser et analyser "
|
||
"les détections de bots générées par le service `bot_detector_ai`.\n\n"
|
||
"**Endpoint clé :** `GET /api/investigation/{ip}/summary` — synthèse complète en un appel.\n\n"
|
||
"Documentation interactive : `/docs` (Swagger UI) · `/redoc` (ReDoc)"
|
||
),
|
||
version="1.0.0",
|
||
openapi_tags=OPENAPI_TAGS,
|
||
lifespan=lifespan,
|
||
)
|
||
|
||
# Configuration CORS
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=settings.CORS_ORIGINS,
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
# Enregistrement des routes
|
||
app.include_router(metrics.router)
|
||
app.include_router(detections.router)
|
||
app.include_router(variability.router)
|
||
app.include_router(attributes.router)
|
||
app.include_router(analysis.router)
|
||
app.include_router(entities.router)
|
||
app.include_router(incidents.router)
|
||
app.include_router(audit.router)
|
||
app.include_router(reputation.router)
|
||
app.include_router(fingerprints.router)
|
||
app.include_router(bruteforce.router)
|
||
app.include_router(tcp_spoofing.router)
|
||
app.include_router(header_fingerprint.router)
|
||
app.include_router(heatmap.router)
|
||
app.include_router(botnets.router)
|
||
app.include_router(rotation.router)
|
||
app.include_router(ml_features.router)
|
||
app.include_router(investigation_summary.router)
|
||
app.include_router(search.router)
|
||
app.include_router(clustering.router)
|
||
|
||
|
||
# Chemin vers le fichier index.html du frontend (utilisé par serve_frontend et serve_spa)
|
||
_FRONTEND_INDEX = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist", "index.html")
|
||
|
||
# Route pour servir le frontend
|
||
@app.get("/")
|
||
async def serve_frontend():
|
||
"""Sert l'application React"""
|
||
if os.path.exists(_FRONTEND_INDEX):
|
||
return FileResponse(_FRONTEND_INDEX)
|
||
return {"message": "Dashboard API - Frontend non construit. Voir /docs pour l'API."}
|
||
|
||
|
||
# Servir les assets statiques
|
||
_assets_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "dist", "assets")
|
||
if os.path.exists(_assets_path):
|
||
try:
|
||
app.mount("/assets", StaticFiles(directory=_assets_path), name="assets")
|
||
except Exception as _e:
|
||
logger.warning(f"Impossible de monter les assets statiques : {_e}")
|
||
|
||
|
||
# Health check
|
||
@app.get("/health")
|
||
async def health_check():
|
||
"""Endpoint de santé pour le health check Docker"""
|
||
try:
|
||
db.connect().ping()
|
||
return {"status": "healthy", "clickhouse": "connected"}
|
||
except Exception as e:
|
||
return {"status": "unhealthy", "clickhouse": "disconnected", "error": str(e)}
|
||
|
||
|
||
# Route catch-all pour le routing SPA (React Router) - DOIT ÊTRE EN DERNIER
|
||
# Sauf pour /api/* qui doit être géré par les routers
|
||
@app.get("/{full_path:path}")
|
||
async def serve_spa(full_path: str):
|
||
"""Redirige toutes les routes vers index.html pour le routing React"""
|
||
# Ne pas intercepter les routes API
|
||
if full_path.startswith("api/"):
|
||
raise HTTPException(status_code=404, detail="API endpoint not found")
|
||
|
||
if os.path.exists(_FRONTEND_INDEX):
|
||
return FileResponse(_FRONTEND_INDEX)
|
||
return {"message": "Dashboard API - Frontend non construit"}
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
uvicorn.run(
|
||
"main:app",
|
||
host=settings.API_HOST,
|
||
port=settings.API_PORT,
|
||
reload=True
|
||
)
|