mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 09:14:33 +00:00
feat(dpi): /exfil serves cumulative R3 devices + engine liveness; webui regen on real engine
/exfil read the idle live-window state.json (empty when the wg-toolbox tunnel is idle) so the dashboard showed no devices; now serves the cumulative rollup (real devices) + overlays live active_flows + an engine-liveness block. Service control repointed from dormant netifyd to the real Go collector (secubox-dpi-flowcap) via a scoped sudoers grant (postinst). Webui regenerated in the cyan hybrid-skin look, framed on the R3 engine, netifyd/mirred/block-rules cruft removed; the XSS-safe media-buffer rendering is preserved verbatim. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
83c8094141
commit
8a01d5f631
|
|
@ -69,17 +69,46 @@ async def health_check():
|
|||
def exfil_state():
|
||||
"""#687 Phase 2 — per-device cloud-exfiltration state produced by the Go
|
||||
collector (secubox-dpi-flowcap → secubox-dpi-collector). Fail-empty so the
|
||||
dashboard never errors before the first capture window completes."""
|
||||
dashboard never errors before the first capture window completes.
|
||||
|
||||
The device list comes from the CUMULATIVE rollup (cumulative.json), which is
|
||||
the set of devices observed across the window — not state.json, which is only
|
||||
the current live window and goes empty whenever the wg-toolbox tunnel is idle
|
||||
(that made the dashboard read "no devices on R3" despite real history). We
|
||||
overlay state.json's active_flows so the live view still updates."""
|
||||
import json as _json
|
||||
from pathlib import Path as _P
|
||||
p = _P("/var/lib/secubox/dpi/state.json")
|
||||
base = {"generated_at": 0, "devices": [], "alerts": [], "alert_count": 0,
|
||||
"top_apps": [], "top_protocols": [], "active_flows": []}
|
||||
cumulative = _P("/var/lib/secubox/dpi/cumulative.json")
|
||||
live = _P("/var/lib/secubox/dpi/state.json")
|
||||
try:
|
||||
if p.exists():
|
||||
return _json.loads(p.read_text())
|
||||
if cumulative.exists():
|
||||
base.update(_json.loads(cumulative.read_text()))
|
||||
except Exception as e: # pragma: no cover
|
||||
return {"generated_at": 0, "devices": [], "alerts": [], "error": str(e)}
|
||||
return {"generated_at": 0, "devices": [], "alerts": [], "alert_count": 0,
|
||||
"note": "no capture window completed yet (or wg-toolbox idle)"}
|
||||
base["error"] = str(e)
|
||||
live_ts = 0
|
||||
try:
|
||||
if live.exists():
|
||||
st = _json.loads(live.read_text())
|
||||
base["active_flows"] = st.get("active_flows", []) or []
|
||||
live_ts = st.get("generated_at", 0) or 0
|
||||
except Exception:
|
||||
pass
|
||||
if not base.get("devices"):
|
||||
base["note"] = "no devices observed yet (or wg-toolbox idle since first capture)"
|
||||
# R3 engine liveness — the collector rewrites state.json every ~60s, so a
|
||||
# recent window means secubox-dpi-flowcap is capturing. Privilege-free (this
|
||||
# API runs unprivileged as `secubox`); netifyd is NOT the engine here.
|
||||
import time as _time
|
||||
age = int(_time.time() - live_ts) if live_ts else None
|
||||
base["engine"] = {
|
||||
"name": "ndpiReader · R3 exfil",
|
||||
"service": "secubox-dpi-flowcap",
|
||||
"alive": bool(age is not None and age < 180),
|
||||
"last_window_s": age,
|
||||
}
|
||||
return base
|
||||
|
||||
|
||||
@app.get("/history")
|
||||
|
|
@ -961,29 +990,35 @@ async def save_settings(req: DpiSettingsRequest, user=Depends(require_jwt)):
|
|||
return {"success": True}
|
||||
|
||||
|
||||
# The R3 DPI engine is the Go flow collector, NOT netifyd (which is dormant on
|
||||
# this platform — see project memory). Service control targets it via a scoped
|
||||
# sudoers grant (this API runs unprivileged as `secubox`).
|
||||
_ENGINE_UNIT = "secubox-dpi-flowcap"
|
||||
|
||||
|
||||
@router.post("/restart")
|
||||
def restart(user=Depends(require_jwt)):
|
||||
"""Redémarrer netifyd."""
|
||||
r = subprocess.run(["systemctl", "restart", "netifyd"], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0}
|
||||
"""Redémarrer le moteur DPI R3 (secubox-dpi-flowcap)."""
|
||||
r = subprocess.run(["sudo", "-n", "systemctl", "restart", _ENGINE_UNIT], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0, "error": r.stderr.strip() or None}
|
||||
|
||||
|
||||
@router.post("/start")
|
||||
def start(user=Depends(require_jwt)):
|
||||
r = subprocess.run(["systemctl", "start", "netifyd"], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0}
|
||||
r = subprocess.run(["sudo", "-n", "systemctl", "start", _ENGINE_UNIT], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0, "error": r.stderr.strip() or None}
|
||||
|
||||
|
||||
@router.post("/stop")
|
||||
def stop(user=Depends(require_jwt)):
|
||||
r = subprocess.run(["systemctl", "stop", "netifyd"], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0}
|
||||
r = subprocess.run(["sudo", "-n", "systemctl", "stop", _ENGINE_UNIT], capture_output=True, text=True)
|
||||
return {"success": r.returncode == 0, "error": r.stderr.strip() or None}
|
||||
|
||||
|
||||
@router.get("/logs")
|
||||
def logs(lines: int = 100, user=Depends(require_jwt)):
|
||||
r = subprocess.run(
|
||||
["journalctl", "-u", "netifyd", "-n", str(lines), "--no-pager"],
|
||||
["journalctl", "-u", _ENGINE_UNIT, "-n", str(lines), "--no-pager"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
return {"lines": r.stdout.splitlines()}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,14 @@ case "$1" in
|
|||
# dpi API (secubox) reads it; keep 0755 so secubox can traverse.
|
||||
install -d -o root -g root -m 0755 /var/lib/secubox/dpi
|
||||
install -d -o root -g root -m 0755 /run/secubox/dpi
|
||||
# The dpi API runs unprivileged as `secubox` but the webui's service
|
||||
# controls (Restart R3 Engine) must (re)start the Go flow collector. Grant a
|
||||
# tightly-scoped sudoers rule for exactly that unit — nothing else.
|
||||
cat > /etc/sudoers.d/secubox-dpi-engine <<'SUDOERS'
|
||||
secubox ALL=(root) NOPASSWD: /usr/bin/systemctl restart secubox-dpi-flowcap, /usr/bin/systemctl start secubox-dpi-flowcap, /usr/bin/systemctl stop secubox-dpi-flowcap
|
||||
SUDOERS
|
||||
chmod 0440 /etc/sudoers.d/secubox-dpi-engine
|
||||
visudo -cf /etc/sudoers.d/secubox-dpi-engine >/dev/null 2>&1 || rm -f /etc/sudoers.d/secubox-dpi-engine
|
||||
systemctl daemon-reload
|
||||
systemctl enable secubox-dpi.service
|
||||
systemctl start secubox-dpi.service || true
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user