From 9bfcbe5d9cbcda952febe31e57f3b16acd415192 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Mon, 27 Jul 2026 08:46:33 +0200 Subject: [PATCH] build(annuaire): package fleet panel + metrics-publish timer + sbx-fleetctl install + /fleet routing (fleet-metrics) Co-Authored-By: Gerald KERMA --- packages/secubox-annuaire/debian/changelog | 18 + packages/secubox-annuaire/debian/postinst | 11 + packages/secubox-annuaire/debian/rules | 12 + .../secubox-annuaire/menu.d/595-fleet.json | 10 + packages/secubox-annuaire/nginx/fleet.conf | 12 + .../systemd/secubox-metrics-publish.service | 13 + .../systemd/secubox-metrics-publish.timer | 11 + .../secubox-annuaire/tests/test_fleet_menu.py | 122 +++++++ .../tests/test_fleet_packaging.py | 103 ++++++ .../secubox-annuaire/www/fleet/index.html | 309 ++++++++++++++++++ secubox.conf.example | 16 + 11 files changed, 637 insertions(+) create mode 100644 packages/secubox-annuaire/menu.d/595-fleet.json create mode 100644 packages/secubox-annuaire/nginx/fleet.conf create mode 100644 packages/secubox-annuaire/systemd/secubox-metrics-publish.service create mode 100644 packages/secubox-annuaire/systemd/secubox-metrics-publish.timer create mode 100644 packages/secubox-annuaire/tests/test_fleet_menu.py create mode 100644 packages/secubox-annuaire/tests/test_fleet_packaging.py create mode 100644 packages/secubox-annuaire/www/fleet/index.html diff --git a/packages/secubox-annuaire/debian/changelog b/packages/secubox-annuaire/debian/changelog index 28190ac1..518708c3 100644 --- a/packages/secubox-annuaire/debian/changelog +++ b/packages/secubox-annuaire/debian/changelog @@ -1,3 +1,21 @@ +secubox-annuaire (0.10.0-1~bookworm1) bookworm; urgency=medium + + * feat(fleet): fleet metrics — every node signs a MetricSnapshot + (CPU/mem/disk/load1/uptime/modules_down/counters) with its own sovereign + node key and publishes it via secubox-metrics-publish.timer (60s, + User=secubox, sbx-fleetctl publish) into a dedicated last-writer-wins + store (/var/lib/secubox/annuaire/fleet/self.json). Peers pull it + verbatim over the existing :8799 mesh listener (GET /fleet/self, public + — mirrors /log/export); GET /fleet (JWT) aggregates self + verified + peers with health/stale annotations. Ships the /fleet panel (matrix of + nodes, health dot, cpu/mem/disk bars, "vu il y a Ns", XSS-strict: + createElement/textContent only, no innerHTML, no inline handlers) and + the menu.d/595-fleet.json entry. `sbx-fleetctl` now actually installs to + /usr/sbin (fixes the prior task's install gap); [metrics] fleet_publish + documented in secubox.conf.example as the per-box opt-out. + + -- Gerald KERMA Mon, 27 Jul 2026 11:00:00 +0200 + secubox-annuaire (0.9.0-1~bookworm1) bookworm; urgency=high * merge: unify the annuaire trust substrate — the assist marketplace layer diff --git a/packages/secubox-annuaire/debian/postinst b/packages/secubox-annuaire/debian/postinst index 84caca7f..d50c121a 100755 --- a/packages/secubox-annuaire/debian/postinst +++ b/packages/secubox-annuaire/debian/postinst @@ -6,6 +6,12 @@ case "$1" in install -d -m 0755 /var/lib/secubox/annuaire /var/log/secubox/annuaire 2>/dev/null || true chown -R secubox:secubox /var/lib/secubox/annuaire /var/log/secubox/annuaire 2>/dev/null || true + # Fleet metrics store (fleet-metrics, Task 5): secubox-metrics-publish.timer + # runs sbx-fleetctl publish as User=secubox, so this specific leaf dir + # (never the shared /var/lib/secubox parent) must be secubox-owned for + # it to write self.json. + install -d -o secubox -g secubox -m 0755 /var/lib/secubox/annuaire/fleet 2>/dev/null || true + # Node-key secret dir (annuairectl init writes node.key here, 0600 secubox). # CSPN: secrets under /etc/secubox/secrets/, 0700, owned by the service user. install -d -m 0700 /etc/secubox/secrets/annuaire 2>/dev/null || true @@ -98,6 +104,11 @@ case "$1" in # The WAF hook: bridge local crowdsec bans into the mesh. Safe no-op # where crowdsec is absent, so enabling by default is fine. systemctl enable --now secubox-threatmesh-bridge.timer 2>/dev/null || true + # Fleet metrics publish (fleet-metrics, Task 5): signs + persists this + # box's own MetricSnapshot every 60s. [metrics] fleet_publish=false in + # secubox.conf makes sbx-fleetctl publish a documented no-op, so + # enabling the timer by default is safe. + systemctl enable --now secubox-metrics-publish.timer 2>/dev/null || true if systemctl is-active --quiet nginx 2>/dev/null; then nginx -t >/dev/null 2>&1 && systemctl reload nginx 2>/dev/null || true fi diff --git a/packages/secubox-annuaire/debian/rules b/packages/secubox-annuaire/debian/rules index f43810ce..2d01b757 100755 --- a/packages/secubox-annuaire/debian/rules +++ b/packages/secubox-annuaire/debian/rules @@ -16,6 +16,7 @@ override_dh_auto_install: install -d $(CURDIR)/debian/secubox-annuaire/etc/nginx/secubox.d install -m 644 $(CURDIR)/nginx/annuaire.conf $(CURDIR)/debian/secubox-annuaire/etc/nginx/secubox.d/ install -m 644 $(CURDIR)/nginx/centers.conf $(CURDIR)/debian/secubox-annuaire/etc/nginx/secubox.d/ + install -m 644 $(CURDIR)/nginx/fleet.conf $(CURDIR)/debian/secubox-annuaire/etc/nginx/secubox.d/ # Authoritative API route (secubox-routes.d) install -d $(CURDIR)/debian/secubox-annuaire/etc/nginx/secubox-routes.d @@ -32,6 +33,9 @@ override_dh_auto_install: # Box operator CLI — delegated center grants (Centres & Grants, Task 7) install -m 755 $(CURDIR)/sbin/sbx-centersctl $(CURDIR)/debian/secubox-annuaire/usr/sbin/sbx-centersctl + # Box operator CLI — fleet metrics publish (fleet-metrics, Task 5) + install -m 755 $(CURDIR)/sbin/sbx-fleetctl $(CURDIR)/debian/secubox-annuaire/usr/sbin/sbx-fleetctl + # Non-local bind sysctl (nginx binds the wg-mesh IP even if iface is late) install -d $(CURDIR)/debian/secubox-annuaire/etc/sysctl.d install -m 644 $(CURDIR)/sysctl/30-secubox-nonlocal-bind.conf $(CURDIR)/debian/secubox-annuaire/etc/sysctl.d/ @@ -44,10 +48,15 @@ override_dh_auto_install: install -d $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/www/centers cp -r $(CURDIR)/www/centers/* $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/www/centers/ + # Web UI (static) — Flotte / fleet metrics panel (fleet-metrics, Task 5) + install -d $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/www/fleet + install -m 644 $(CURDIR)/www/fleet/* $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/www/fleet/ + # Hub menu entry install -d $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/menu.d install -m 644 $(CURDIR)/menu.d/575-annuaire.json $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/menu.d/ install -m 644 $(CURDIR)/menu.d/570-centers.json $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/menu.d/ + install -m 644 $(CURDIR)/menu.d/595-fleet.json $(CURDIR)/debian/secubox-annuaire/usr/share/secubox/menu.d/ # systemd service install -d $(CURDIR)/debian/secubox-annuaire/usr/lib/systemd/system @@ -63,6 +72,9 @@ override_dh_auto_install: install -m 755 $(CURDIR)/sbin/sbx-threatmesh-bridge $(CURDIR)/debian/secubox-annuaire/usr/sbin/sbx-threatmesh-bridge install -m 644 $(CURDIR)/systemd/secubox-threatmesh-bridge.service $(CURDIR)/debian/secubox-annuaire/usr/lib/systemd/system/ install -m 644 $(CURDIR)/systemd/secubox-threatmesh-bridge.timer $(CURDIR)/debian/secubox-annuaire/usr/lib/systemd/system/ + # Fleet metrics publish timer + oneshot (fleet-metrics, Task 5) + install -m 644 $(CURDIR)/systemd/secubox-metrics-publish.service $(CURDIR)/debian/secubox-annuaire/usr/lib/systemd/system/ + install -m 644 $(CURDIR)/systemd/secubox-metrics-publish.timer $(CURDIR)/debian/secubox-annuaire/usr/lib/systemd/system/ install -d $(CURDIR)/debian/secubox-annuaire/etc/secubox install -m 644 $(CURDIR)/conf/annuaire-apply.conf.example $(CURDIR)/debian/secubox-annuaire/etc/secubox/ diff --git a/packages/secubox-annuaire/menu.d/595-fleet.json b/packages/secubox-annuaire/menu.d/595-fleet.json new file mode 100644 index 00000000..f386015c --- /dev/null +++ b/packages/secubox-annuaire/menu.d/595-fleet.json @@ -0,0 +1,10 @@ +{ + "id": "fleet", + "name": "Flotte", + "icon": "🛰", + "path": "/fleet/", + "category": "mesh", + "order": 595, + "description": "Vue mesh des instantanés de métriques signés par nœud — CPU/mém/disque/load, modules down, compteurs", + "requires": ["secubox-annuaire"] +} diff --git a/packages/secubox-annuaire/nginx/fleet.conf b/packages/secubox-annuaire/nginx/fleet.conf new file mode 100644 index 00000000..18e9661a --- /dev/null +++ b/packages/secubox-annuaire/nginx/fleet.conf @@ -0,0 +1,12 @@ +# /etc/nginx/secubox.d/fleet.conf — Installed by secubox-annuaire. +# Static "Flotte" UI (Task 5, fleet-metrics). It calls the same +# /api/v1/annuaire/ API prefix as www/annuaire/ and www/centers/ (routed by +# secubox-routes.d/annuaire-routes.conf, already installed) — no separate +# API route needed here; /fleet/self and /fleet ride the existing annuaire +# API mount. + +location /fleet/ { + alias /usr/share/secubox/www/fleet/; + index index.html; + try_files $uri $uri/ /fleet/index.html; +} diff --git a/packages/secubox-annuaire/systemd/secubox-metrics-publish.service b/packages/secubox-annuaire/systemd/secubox-metrics-publish.service new file mode 100644 index 00000000..1e03e2f9 --- /dev/null +++ b/packages/secubox-annuaire/systemd/secubox-metrics-publish.service @@ -0,0 +1,13 @@ +[Unit] +Description=SecuBox Annuaire — publish this node's signed fleet metrics snapshot (fleet-metrics) +Documentation=file:///usr/share/doc/secubox-annuaire/README +After=secubox-annuaire.service +Wants=secubox-annuaire.service + +[Service] +Type=oneshot +User=secubox +Group=secubox +# Signs with the box's own node.key (owned by secubox) — never root. A +# missing/disabled config degrades to a no-op (see sbx-fleetctl --help). +ExecStart=/usr/sbin/sbx-fleetctl publish diff --git a/packages/secubox-annuaire/systemd/secubox-metrics-publish.timer b/packages/secubox-annuaire/systemd/secubox-metrics-publish.timer new file mode 100644 index 00000000..a47b6181 --- /dev/null +++ b/packages/secubox-annuaire/systemd/secubox-metrics-publish.timer @@ -0,0 +1,11 @@ +[Unit] +Description=SecuBox Annuaire — periodic fleet metrics publish (fleet-metrics) + +[Timer] +OnBootSec=90s +OnUnitActiveSec=60s +RandomizedDelaySec=10s +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/packages/secubox-annuaire/tests/test_fleet_menu.py b/packages/secubox-annuaire/tests/test_fleet_menu.py new file mode 100644 index 00000000..703f2278 --- /dev/null +++ b/packages/secubox-annuaire/tests/test_fleet_menu.py @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: LicenseRef-CMSD-1.0 +# Copyright (c) 2026 CyberMind — Gérald Kerma +# Source-Disclosed License — All rights reserved except as expressly granted. +# See LICENCE-CMSD-1.0.md for terms. +""" +SecuBox-Deb :: secubox-annuaire :: tests/test_fleet_menu.py +Menu.d entry + /fleet panel coverage (Task 5, feat/fleet-metrics). + +The panel is XSS-STRICT (stricter than sibling panels like www/centers): +NO inline on*= handlers (event delegation only) AND NO innerHTML anywhere +for API-sourced data — rows are built with createElement/textContent/dataset +only. Mirrors the skin (hybrid-dark, sidebar.js, sbx_token) of +www/centers/index.html. +""" +import json +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +MENU_PATH = ROOT / "menu.d" / "595-fleet.json" +HTML_PATH = ROOT / "www" / "fleet" / "index.html" + +VALID_CATEGORIES = {"auth", "wall", "boot", "mind", "root", "mesh"} + + +def _html() -> str: + return HTML_PATH.read_text() + + +# ── menu.d/595-fleet.json ──────────────────────────────────────────────── + +def test_menu_valid(): + m = json.loads(MENU_PATH.read_text()) + assert m.get("category") in VALID_CATEGORIES + assert "/fleet" in json.dumps(m) + + +def test_menu_has_name_and_category_keys(): + m = json.loads(MENU_PATH.read_text()) + assert m.get("name") + assert m.get("category") == "mesh" + + +def test_menu_path_is_fleet(): + m = json.loads(MENU_PATH.read_text()) + assert m.get("path") == "/fleet/" + + +# ── panel skin / navbar ────────────────────────────────────────────────── + +def test_panel_uses_shared_skin_and_sidebar(): + html = _html() + assert '