feat(profiles): four functional tiers (full/lite/secure-gateway/media-lab) + seed

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
CyberMind-FR 2026-07-18 11:09:38 +02:00
parent 9581e0edd6
commit b72162b881
7 changed files with 109 additions and 0 deletions

View File

@ -1,4 +1,5 @@
api/*.py usr/lib/secubox/profiles/api/
profiles/*.toml usr/share/secubox/profiles/
sbin/secubox-profilectl usr/sbin/
www/profiles/index.html usr/share/secubox/www/profiles/
menu.d/11-profiles.json usr/share/secubox/menu.d/

View File

@ -15,6 +15,16 @@ case "$1" in
# Phase 1 (scan) and is left untouched here.
install -d -o secubox -g secubox -m 755 /etc/secubox/profiles
# Seed reference profiles into the operator-editable dir on first
# install only — never overwrite an edited profile on upgrade.
for f in /usr/share/secubox/profiles/*.toml; do
[ -e "$f" ] || continue
dest="/etc/secubox/profiles/$(basename "$f")"
if [ ! -e "$dest" ]; then
install -o secubox -g secubox -m 644 "$f" "$dest"
fi
done
# Own dedicated socket, own service — never served by the aggregator
# (the profile engine must not depend on something it may later
# restart). Restart (not just enable) so an upgrade picks up new code.

View File

@ -0,0 +1,24 @@
name = "full"
label = "Full — loadout courant (baseline)"
on = [
"ad-guard", "admin", "aggregator", "annuaire", "appstore", "auth",
"avatar", "backup", "billets", "c3box", "cdn", "certs",
"cloner", "config-advisor", "cookies", "core", "crowdsec", "cs-bridge",
"cve-triage", "cyberfeed", "dns", "dns-guard", "dns-provider", "dpi",
"dpi-flowcap", "droplet", "exposure", "eye-remote", "eye-remote-dhcp", "fmrelay",
"frigate", "gitea", "glances", "grafana", "haproxy", "hardening",
"health-doctor", "health-prober", "hub", "identity", "interceptor", "ipblock",
"ksm", "led-heartbeat", "led-trigger", "leds", "localrecall", "lyrion",
"mail", "mcp-server", "mesh", "meshname", "metablogizer", "metabolizer",
"metacatalog", "metoblizer", "metrics", "mirror", "mitmproxy", "modem",
"module-prober", "mqtt", "nac", "ndpid", "netboot", "netdata",
"netdiag", "netifyd", "netmodes", "nettweak", "network-anomaly", "network-ready",
"nextcloud", "p2p", "peertube", "photoprism", "podcaster", "portal",
"proxypac", "publish", "qos", "reality", "repo", "reporter",
"rezapp", "roadmap", "routes", "rtty", "runtime", "rustdesk",
"saas-relay", "security-posture", "soc", "streamforge", "streamlit", "streamlit-routes",
"system", "threat-analyst", "threatmesh", "threats", "toolbox", "toolbox-mitm",
"tor", "traffic", "turn", "users", "vault", "vhost",
"vm", "vortex-dns", "vortex-firewall", "waf", "waf-ng", "waf-ratelimit",
"watchdog", "wireguard", "yacy", "zigbee", "zkp",
]

View File

@ -0,0 +1,11 @@
name = "lite"
label = "Lite — daily-driver optimisé"
on = [
"ad-guard", "admin", "aggregator", "annuaire", "auth", "backup",
"cdn", "certs", "core", "crowdsec", "cs-bridge", "dns",
"dns-guard", "dns-provider", "exposure", "haproxy", "hardening", "health-doctor",
"hub", "ipblock", "led-trigger", "leds", "mesh", "netdata",
"network-ready", "p2p", "portal", "routes", "runtime", "system",
"users", "vhost", "vortex-firewall", "waf", "waf-ng", "waf-ratelimit",
"watchdog", "wireguard",
]

View File

@ -0,0 +1,22 @@
name = "media-lab"
label = "Media Lab — box maison-média"
on = [
"ad-guard", "admin", "aggregator", "annuaire", "appstore", "auth",
"avatar", "backup", "billets", "c3box", "cdn", "certs",
"cloner", "config-advisor", "cookies", "core", "crowdsec", "cs-bridge",
"cyberfeed", "dns", "dns-guard", "dns-provider", "dpi", "dpi-flowcap",
"droplet", "exposure", "eye-remote", "eye-remote-dhcp", "fmrelay", "frigate",
"gitea", "glances", "grafana", "haproxy", "hardening", "health-doctor",
"health-prober", "hub", "identity", "ipblock", "ksm", "led-heartbeat",
"led-trigger", "leds", "localrecall", "lyrion", "mail", "mcp-server",
"mesh", "meshname", "metablogizer", "metacatalog", "metrics", "mirror",
"mitmproxy", "modem", "module-prober", "mqtt", "nac", "ndpid",
"netboot", "netdata", "netdiag", "netifyd", "netmodes", "nettweak",
"network-ready", "nextcloud", "p2p", "peertube", "photoprism", "podcaster",
"portal", "proxypac", "publish", "qos", "reality", "repo",
"reporter", "rezapp", "roadmap", "routes", "rtty", "runtime",
"rustdesk", "saas-relay", "streamforge", "streamlit", "streamlit-routes", "system",
"threats", "tor", "traffic", "turn", "users", "vault",
"vhost", "vm", "vortex-dns", "vortex-firewall", "waf", "waf-ng",
"waf-ratelimit", "watchdog", "wireguard", "yacy", "zigbee", "zkp",
]

View File

@ -0,0 +1,7 @@
name = "secure-gateway"
label = "Secure Gateway — appliance durcie"
on = [
"aggregator", "auth", "certs", "core", "crowdsec", "dns",
"dns-guard", "dns-provider", "exposure", "haproxy", "hardening", "ipblock",
"users", "vortex-firewall", "waf", "waf-ng", "waf-ratelimit",
]

View File

@ -0,0 +1,34 @@
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
# Source-Disclosed License — All rights reserved except as expressly granted.
# See LICENCE-CMSD-1.0.md for terms.
from pathlib import Path
import pytest
from api.state import load_profile
PROFILES_DIR = Path(__file__).resolve().parent.parent / "profiles"
PROFILE_FILES = sorted(PROFILES_DIR.glob("*.toml"))
EXPECTED = {"full", "lite", "secure-gateway", "media-lab"}
def test_all_four_profiles_ship():
assert {p.stem for p in PROFILE_FILES} == EXPECTED
@pytest.mark.parametrize("path", PROFILE_FILES, ids=lambda p: p.stem)
def test_profile_loads_and_name_matches_stem(path):
prof = load_profile(path) # raises StateError if name != stem or bad 'on'
assert prof.name == path.stem
assert prof.on, f"{path.stem}: empty on-list"
for mid in prof.on:
assert mid and mid == mid.strip() and " " not in mid, f"bad id {mid!r}"
def test_protected_core_present_in_every_profile():
# aggregator/auth/core are protected (always ON) but every functional tier
# lists them explicitly for readability; guard that nobody drops them.
for path in PROFILE_FILES:
on = load_profile(path).on
assert {"core", "auth", "aggregator"} <= on, f"{path.stem} missing protected"