From 2d04288e95f81afbd0c9605a98b5778a3c497d3c Mon Sep 17 00:00:00 2001 From: toto Date: Thu, 9 Apr 2026 00:29:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20SOC=20workflow=20overhaul=20?= =?UTF-8?q?=E2=80=94=20sidebar=20nav,=20doc=20tooltips,=20full-width=20lay?= =?UTF-8?q?out?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - base.html: collapsible sidebar navigation, doc tooltip system, JS helpers (fmtNum, fmtPct, fmtDuration, ecGrid, buildTable, docHTML) - overview.html: SOC command center with stacked timeline, live alerts, campaigns panel, browser donut, 6 KPIs - detections.html: threat color dots, raw score column, click-to-navigate rows - network.html: JA4 rotation, brute-force, persistent threats tables, 6 KPIs - ip_detail.html: ASN/country KPIs, AE/XGB/campaign columns, enriched features - scores/traffic/features/models/classify: page_title blocks + doc tooltips - api.py: 9 new endpoints (campaigns, brute-force, ja4-rotation, recurrence, cascade, alerts, timeline-detail, ua-rotation) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- services/dashboard/backend/routes/api.py | 184 ++++++++ .../dashboard/backend/templates/base.html | 283 ++++++++---- .../dashboard/backend/templates/classify.html | 12 +- .../backend/templates/detections.html | 124 ++--- .../dashboard/backend/templates/features.html | 10 +- .../backend/templates/ip_detail.html | 216 +++++---- .../dashboard/backend/templates/models.html | 10 +- .../dashboard/backend/templates/network.html | 433 +++++++++-------- .../dashboard/backend/templates/overview.html | 436 +++++++++++------- .../dashboard/backend/templates/scores.html | 11 +- .../dashboard/backend/templates/traffic.html | 10 +- 11 files changed, 1137 insertions(+), 592 deletions(-) diff --git a/services/dashboard/backend/routes/api.py b/services/dashboard/backend/routes/api.py index 611b879..826eebc 100644 --- a/services/dashboard/backend/routes/api.py +++ b/services/dashboard/backend/routes/api.py @@ -803,3 +803,187 @@ async def classifications() -> dict[str, Any]: except Exception as exc: logger.exception("classifications query failed") return {"data": []} + + +# --------------------------------------------------------------------------- +# GET /api/campaigns — HDBSCAN bot campaign clusters +# --------------------------------------------------------------------------- +@router.get("/campaigns") +async def campaigns() -> dict[str, Any]: + """Campagnes de bots détectées par clustering HDBSCAN.""" + try: + rows = query( + f"SELECT campaign_id, " + f"count() AS members, " + f"min(detected_at) AS first_seen, max(detected_at) AS last_seen, " + f"avg(anomaly_score) AS avg_score, " + f"max(anomaly_score) AS max_score, " + f"uniqExact(src_ip) AS unique_ips, " + f"groupUniqArray(10)(ja4) AS ja4_list, " + f"groupUniqArray(5)(asn_org) AS asn_list, " + f"groupUniqArray(5)(country_code) AS countries " + f"FROM {_DB}.ml_detected_anomalies " + "WHERE campaign_id != '' AND campaign_id != '0' " + "AND detected_at >= now() - INTERVAL 7 DAY " + "GROUP BY campaign_id " + "ORDER BY members DESC LIMIT 50" + ) + return {"campaigns": rows} + except Exception as exc: + logger.exception("campaigns query failed") + return {"campaigns": []} + + +# --------------------------------------------------------------------------- +# GET /api/brute-force — Form brute-force detection +# --------------------------------------------------------------------------- +@router.get("/brute-force") +async def brute_force() -> dict[str, Any]: + """Détection de brute-force / credential stuffing via view_form_bruteforce_detected.""" + try: + rows = query( + f"SELECT toString(src_ip) AS src_ip, host, " + f"post_count, distinct_paths, first_seen, last_seen " + f"FROM {_DB}.view_form_bruteforce_detected " + "ORDER BY post_count DESC LIMIT 100" + ) + return {"data": rows} + except Exception as exc: + logger.exception("brute-force query failed") + return {"data": []} + + +# --------------------------------------------------------------------------- +# GET /api/ja4-rotation — JA4 fingerprint rotation detection +# --------------------------------------------------------------------------- +@router.get("/ja4-rotation") +async def ja4_rotation() -> dict[str, Any]: + """IPs présentant une rotation de fingerprints JA4 (évasion potentielle).""" + try: + rows = query( + f"SELECT toString(src_ip) AS src_ip, host, " + f"distinct_ja4, ja4_list, total_hits, window_start " + f"FROM {_DB}.view_host_ip_ja4_rotation " + "ORDER BY distinct_ja4 DESC LIMIT 100" + ) + return {"data": rows} + except Exception as exc: + logger.exception("ja4-rotation query failed") + return {"data": []} + + +# --------------------------------------------------------------------------- +# GET /api/recurrence — Persistent threat IPs +# --------------------------------------------------------------------------- +@router.get("/recurrence") +async def recurrence() -> dict[str, Any]: + """IPs récurrentes détectées sur plusieurs fenêtres temporelles.""" + try: + rows = query( + f"SELECT toString(src_ip) AS src_ip, " + f"recurrence, worst_score, worst_threat, " + f"first_seen, last_seen, " + f"top_ja4, top_host " + f"FROM {_DB}.view_ip_recurrence " + "ORDER BY recurrence DESC, worst_score DESC LIMIT 100" + ) + return {"data": rows} + except Exception as exc: + logger.exception("recurrence query failed") + return {"data": []} + + +# --------------------------------------------------------------------------- +# GET /api/cascade/{ip} — Resource cascade for headless detection +# --------------------------------------------------------------------------- +@router.get("/cascade/{ip}") +async def cascade(ip: str) -> dict[str, Any]: + """Cascade de ressources (détection navigateurs headless) pour une IP.""" + clean_ip = ip.replace("::ffff:", "") + try: + rows = query( + f"SELECT toString(src_ip) AS src_ip, host, " + f"page_count, avg_sub_delay_ms, stddev_sub_delay_ms, " + f"max_sub_resources, window_start " + f"FROM {_DB}.view_resource_cascade_1h " + "WHERE src_ip = toIPv6({ip:String}) " + "ORDER BY window_start DESC LIMIT 50", + {"ip": clean_ip}, + ) + return {"data": rows} + except Exception as exc: + logger.exception("cascade query failed for %s", ip) + return {"data": []} + + +# --------------------------------------------------------------------------- +# GET /api/alerts — Live alert feed (recent HIGH/CRITICAL) +# --------------------------------------------------------------------------- +@router.get("/alerts") +async def alerts( + limit: int = Query(20, ge=1, le=100), +) -> dict[str, Any]: + """Flux d'alertes en temps réel (CRITICAL, HIGH, KNOWN_BOT).""" + try: + rows = query( + f"SELECT detected_at, toString(src_ip) AS src_ip, " + f"anomaly_score, threat_level, ja4, host, " + f"asn_org, country_code, bot_name, campaign_id, " + f"hits, hit_velocity, reason " + f"FROM {_DB}.ml_detected_anomalies " + "WHERE detected_at >= now() - INTERVAL 1 DAY " + "ORDER BY detected_at DESC " + f"LIMIT {{lim:UInt32}}", + {"lim": limit}, + ) + return {"alerts": rows} + except Exception as exc: + logger.exception("alerts query failed") + return {"alerts": []} + + +# --------------------------------------------------------------------------- +# GET /api/timeline-detail — Hourly threat-level breakdown +# --------------------------------------------------------------------------- +@router.get("/timeline-detail") +async def timeline_detail() -> dict[str, Any]: + """Timeline horaire avec ventilation par threat level.""" + try: + rows = query( + f"SELECT toStartOfHour(detected_at) AS hour, " + f"threat_level, count() AS cnt " + f"FROM {_DB}.ml_detected_anomalies " + "WHERE detected_at >= now() - INTERVAL 1 DAY " + "GROUP BY hour, threat_level " + "ORDER BY hour" + ) + # Pivot: group by hour + hours: dict[str, dict] = {} + for r in rows: + h = str(r["hour"]) + if h not in hours: + hours[h] = {"hour": h} + hours[h][r["threat_level"]] = r["cnt"] + return {"timeline": list(hours.values())} + except Exception as exc: + logger.exception("timeline-detail query failed") + return {"timeline": []} + + +# --------------------------------------------------------------------------- +# GET /api/ua-rotation — User-Agent rotation detection +# --------------------------------------------------------------------------- +@router.get("/ua-rotation") +async def ua_rotation() -> dict[str, Any]: + """IPs avec rotation de User-Agent (évasion potentielle).""" + try: + rows = query( + f"SELECT toString(src_ip) AS src_ip, ja4, " + f"distinct_ua_count, ua_samples, total_requests, window_start " + f"FROM {_DB}.view_dashboard_user_agents " + "ORDER BY distinct_ua_count DESC LIMIT 100" + ) + return {"data": rows} + except Exception as exc: + logger.exception("ua-rotation query failed") + return {"data": []} diff --git a/services/dashboard/backend/templates/base.html b/services/dashboard/backend/templates/base.html index c18c893..4f0082e 100644 --- a/services/dashboard/backend/templates/base.html +++ b/services/dashboard/backend/templates/base.html @@ -16,28 +16,30 @@ extend: { colors: { brand: { 50:'#eef2ff',100:'#e0e7ff',500:'#6366f1',600:'#4f46e5',700:'#4338ca',900:'#312e81' }, + surface: { 800:'#1e293b', 900:'#0f172a', 950:'#020617' }, }, - fontFamily: { - sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'], - }, + fontFamily: { sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'] }, } } } {% block head %}{% endblock %} - - - - -
- {% block content %}{% endblock %} -
+ + + +
+ +
+
+

{% block page_title %}{% endblock %}

+
+ {% block header_actions %}{% endblock %} +
+
+
+ {% block content %}{% endblock %} +
+
+ {% block scripts %}{% endblock %} diff --git a/services/dashboard/backend/templates/classify.html b/services/dashboard/backend/templates/classify.html index 3284221..fdf8748 100644 --- a/services/dashboard/backend/templates/classify.html +++ b/services/dashboard/backend/templates/classify.html @@ -1,8 +1,16 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Classifier{% endblock %} +{% block page_title %} + Classification SOC +
+

Feedback analyste SOC

+

Classifiez les IPs pour entraîner le modèle XGBoost supervisé. Les labels sont utilisés au prochain cycle ML.

+

Bot : Confirme que l'IP est malveillante. Légitime : Faux positif. Suspect : À surveiller.

+

Source : soc_feedback → XGBoost training

+
+{% endblock %} {% block content %} -
-

Classification SOC

+
diff --git a/services/dashboard/backend/templates/detections.html b/services/dashboard/backend/templates/detections.html index 30264b3..c56c37f 100644 --- a/services/dashboard/backend/templates/detections.html +++ b/services/dashboard/backend/templates/detections.html @@ -1,61 +1,73 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Détections{% endblock %} +{% block page_title %} + Détections d'anomalies +
+

Table des détections

+

Toutes les sessions classées comme anomaliques par l'ensemble ML triple-voix (EIF + Autoencoder + XGBoost). Inclut les bots connus identifiés par dictionnaire.

+

Workflow : Filtrez par threat level → triez par score → cliquez sur une IP pour l'investiguer → classifiez via le bouton rapide.

+

Source : ml_detected_anomalies (30 derniers jours)

+
+{% endblock %} {% block content %} -
- -
-
-

Détections par threat level

-
+
+ +
+
+
Par threat level
+
-
-

Top 5 raisons de détection

-
+
+
Top raisons
+
-
-

Top 5 ASN détectés

-
+
+
Top ASN détectés
+
+
-

Anomalies détectées

- +
- +
-
-
- + +
+
+
+ + - + - - + +
Date ↕ IP Score ↕Raw Threat JA4 HostHitsHits ↕ ASN PaysRécurrenceRaisonBotRéc.
-
+
- - + +
@@ -63,13 +75,12 @@ {% endblock %} {% block scripts %} {% endblock %} diff --git a/services/dashboard/backend/templates/models.html b/services/dashboard/backend/templates/models.html index 6b8d708..d643cff 100644 --- a/services/dashboard/backend/templates/models.html +++ b/services/dashboard/backend/templates/models.html @@ -1,8 +1,16 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Modèles{% endblock %} +{% block page_title %} + Modèles ML +
+

État des modèles ML

+

Ensemble triple-voix : Extended Isolation Forest (EIF) + Autoencoder (AE) + XGBoost supervisé.

+

Versions : Chaque cycle crée un nouveau modèle si une dérive est détectée (95% features). Les anciens modèles restent en cache.

+

Source : /data/models/*.json, ml_all_scores

+
+{% endblock %} {% block content %}
-

État des modèles ML

Statistiques de scoring (7 derniers jours)

diff --git a/services/dashboard/backend/templates/network.html b/services/dashboard/backend/templates/network.html index e89f948..3082508 100644 --- a/services/dashboard/backend/templates/network.html +++ b/services/dashboard/backend/templates/network.html @@ -1,95 +1,163 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Analyse Réseau{% endblock %} +{% block page_title %} + Analyse Réseau +
+

Analyse réseau

+

Vue complète de l'infrastructure réseau : ASN, pays, fingerprints JA4, rotation de fingerprints, brute-force et menaces persistantes.

+

Workflow : Identifiez les ASN suspects → vérifiez la rotation JA4 → contrôlez le brute-force → investiguez les IPs récurrentes.

+

Sources : view_ai_features_1h, view_host_ip_ja4_rotation, view_form_bruteforce_detected, view_ip_recurrence

+
+{% endblock %} {% block content %} -
-

Analyse Réseau

- +
-
-
-
Pays
-
-
-
-
ASNs
-
-
-
-
Sessions humaines
-
-
-
-
Sessions datacenter
-
-
+
+
Pays
+
ASNs
+
Sessions ISP
+
Sessions DC
+
Rotation JA4
+
Brute-force
- +
-
-

Treemap ASN (par label)

-
+
+
Treemap ASN +
+

Treemap ASN

+

Taille = nombre de sessions. Regroupé par type : ISP (résidentiel), Datacenter, Hosting, CDN.

+

Action : Un ASN datacenter avec beaucoup de sessions mérite investigation.

+

Source : view_ai_features_1h

+
+
+
-
-

Sunburst Pays → Label

-
+
+
Pays → Type ASN +
+

Sunburst géographique

+

Niveau 1 : pays. Niveau 2 : type d'ASN. Identifiez les pays avec forte proportion datacenter.

+

Source : view_ai_features_1h

+
+
+
- -
-

Empreintes JA4

-
- - - - - - - - - - - - -
JA4 ▸Sessions ▸Hits ▸Avg Velocity ▸Avg Fuzz ▸Browser Score ▸LabelBot
-
-
- - -
-
-

Détail ASN

-
- - - - - - -
ASN OrgLabelPaysSessionsHitsAvg VelocityAvg Fuzz
+ +
+ +
+
+ + Rotation JA4 (évasion TLS) +
+

Rotation de fingerprints JA4

+

IPs utilisant plusieurs fingerprints TLS distinctes sur une fenêtre horaire. Indique une tentative d'évasion de détection (rotation de client TLS).

+

Seuil critique : ≥ 3 JA4 distincts par IP/host/heure.

+

Source : view_host_ip_ja4_rotation

+
+
+
+
+ + +
IPHostJA4 distinctsHitsFenêtre
+
-
-

Bots par empreinte

-
+ +
+
+ + Brute-force / Credential stuffing +
+

Détection brute-force

+

IPs envoyant ≥10 requêtes POST par host sur 24h. Indique du credential stuffing ou du brute-force de formulaires.

+

Source : view_form_bruteforce_detected

+
+
+
+
+ + +
IPHostPOSTsPathsPremièreDernière
+
+
+
+
+ + +
+ +
+
+ + Menaces persistantes +
+

IPs récurrentes

+

IPs détectées sur plusieurs fenêtres horaires. Récurrence élevée = acteur persistant. Le score indique le pire score observé.

+

Source : view_ip_recurrence

+
+
+
+
+ + +
IPRéc.ScoreThreat
+
+
+
+ +
+
Empreintes JA4 +
+

Fingerprints TLS (JA4)

+

Chaque combinaison unique de paramètres TLS génère un hash JA4. Les navigateurs courants partagent des fingerprints connues.

+

Indicateurs : Velocity élevée + browser score bas = bot probable.

+

Source : view_ai_features_1h GROUP BY ja4

+
+
+
+
+ + + + + + + + +
JA4SessionsHitsVelocityFuzzBrowserLabelBot
+
+
+
+
+ + +
+
+
Détail ASN
+
+
+ + +
ASN OrgLabelPaysSessionsHitsVelocityFuzz
+
+
+
+
+
Bots par empreinte
+
{% endblock %} {% block scripts %} {% endblock %} diff --git a/services/dashboard/backend/templates/overview.html b/services/dashboard/backend/templates/overview.html index c52c2ae..801918e 100644 --- a/services/dashboard/backend/templates/overview.html +++ b/services/dashboard/backend/templates/overview.html @@ -1,87 +1,184 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Overview{% endblock %} +{% block page_title %} + Centre de commande +
+

Centre de commande SOC

+

Vue d'ensemble temps réel de la posture de sécurité. Les KPI montrent les dernières 24h. La timeline et les alertes se rafraîchissent toutes les 60s.

+

Workflow : Identifiez les pics → cliquez sur une alerte → investiguez l'IP → classifiez.

+

Sources : ml_detected_anomalies, ml_all_scores, http_logs

+
+{% endblock %} {% block content %} -
- -
+
+ +
-
Détections 24h
+
Détections 24h
+
-
Sessions scorées
-
-
-
-
Trafic total 24h
-
-
-
-
IPs uniques
-
-
-
-
Critical / High
+
Critical + High
-
Modèles actifs
-
+
Sessions ML
+
+
+
+
IPs uniques
+
+
+
+
Navigateurs légit.
+
+
+
+
Bots connus
+
- + +
+ +
+
+ + + Timeline détections (24h) +
+

Timeline des détections

+

Courbes empilées par niveau de menace. Un pic soudain indique une attaque en cours ou un nouveau pattern détecté.

+

Action : Cliquez sur un pic pour filtrer les détections de cette heure.

+

Source : ml_detected_anomalies GROUP BY hour, threat_level

+
+
+
+
+
+ +
+
+ + + Alertes récentes +
+

Flux d'alertes temps réel

+

Dernières détections HIGH/CRITICAL/KNOWN_BOT. Cliquez sur une IP pour démarrer l'investigation.

+

Source : ml_detected_anomalies ORDER BY detected_at DESC

+
+
+
+
+
Chargement…
+
+
+
+ + +
+
+
+ Threat Levels +
+

Distribution des menaces

+

Répartition des sessions par niveau : CRITICAL (score >0.70), HIGH (>0.40), MEDIUM (>0.10), NORMAL, LEGITIMATE_BROWSER, KNOWN_BOT.

+

Action : Cliquez sur un segment pour filtrer les détections.

+

Source : ml_all_scores.threat_level

+
+
+
+
+
+
+
+ Navigateurs +
+

Familles de navigateurs

+

Identification via dictionnaire JA4 → browser_family. Les navigateurs légitimes sont exemptés du scoring ML.

+

Source : ml_all_scores.browser_family

+
+
+
+
+
+
+
+ Top 10 IPs suspectes +
+

IPs les plus détectées

+

IPs avec le plus grand nombre de détections sur 24h. Le score indique le pire score observé.

+

Action : Cliquez sur une IP pour l'investiguer en profondeur.

+

Source : ml_detected_anomalies GROUP BY src_ip

+
+
+
+
+
+ + + +
IPDét.ScoreThreatASNPays
+
+
+
+
+ +
-
-

Détections par heure (24h)

-
+
+
+ Top ASN +
+

Systèmes autonomes

+

ASN les plus représentés. Couleur : ISP (résidentiel), Datacenter, Hosting.

+

Action : Cliquez pour voir les détails réseau.

+

Source : view_ai_features_1h.asn_org

+
+
+
+
-
-

Threat levels

-
+
+
+ Géographie +
+

Répartition géographique

+

Treemap par pays et type d'ASN. La taille indique le nombre de sessions.

+

Source : view_ai_features_1h.country_code

+
+
+
+
-
- - -
-
-

Répartition par ASN

-
-
-
-

Répartition géographique

-
-
-
- - -
-
-

Empreintes JA4 (top 15)

-
-
-
-

Bots identifiés

-
-
-
- - -
-

Top 10 IPs détectées (24h)

-
- - - -
IPDétectionsPire scoreThreat LevelASNPays
+
+
+ + + Campagnes bots +
+

Campagnes détectées (HDBSCAN)

+

Groupes d'IPs présentant des patterns comportementaux similaires, identifiés par clustering HDBSCAN sur les features ML.

+

Action : Une campagne multi-IP indique une attaque coordonnée (botnet, scraping distribué).

+

Source : ml_detected_anomalies.campaign_id

+
+
+
+
+
+
Chargement…
+
+
{% endblock %} {% block scripts %} {% endblock %} diff --git a/services/dashboard/backend/templates/scores.html b/services/dashboard/backend/templates/scores.html index 5ba8ddb..4098a3c 100644 --- a/services/dashboard/backend/templates/scores.html +++ b/services/dashboard/backend/templates/scores.html @@ -1,5 +1,15 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Scores ML{% endblock %} +{% block page_title %} + Scores ML +
+

Toutes les classifications ML

+

Chaque session analysée reçoit un score composite (EIF + AE + XGBoost). Cette vue montre TOUTES les sessions, pas seulement les anomalies.

+

AE Error : Erreur de reconstruction autoencoder (élevé = inhabituel).

+

XGB Prob : Probabilité supervisée (entraîné sur labels SOC).

+

Source : ml_all_scores (3 derniers jours)

+
+{% endblock %} {% block content %}
@@ -14,7 +24,6 @@
-

Toutes les classifications ML

diff --git a/services/dashboard/backend/templates/traffic.html b/services/dashboard/backend/templates/traffic.html index ebf16b1..727e2a0 100644 --- a/services/dashboard/backend/templates/traffic.html +++ b/services/dashboard/backend/templates/traffic.html @@ -1,5 +1,14 @@ {% extends "base.html" %} {% block title %}JA4 SOC — Trafic HTTP{% endblock %} +{% block page_title %} + Trafic HTTP +
+

Logs HTTP bruts

+

Toutes les requêtes HTTP capturées (24h). Filtrez par méthode, host ou status pour identifier les patterns suspects.

+

Workflow : Filtrez POST → cherchez du brute-force → cliquez sur l'IP → investiguez.

+

Source : http_logs (24h)

+
+{% endblock %} {% block content %}
@@ -18,7 +27,6 @@
-

Logs HTTP (24h)