mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 11:12:29 +00:00
feat(profiles): waf-sync — on-demand-vhost list for sbxwaf wake trigger (ref #896)
Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
9d4da5bf67
commit
2008b9c29d
58
packages/secubox-profiles/api/wafsync.py
Normal file
58
packages/secubox-profiles/api/wafsync.py
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
SecuBox-Deb :: profiles — génération de la liste on-demand-vhosts pour sbxwaf
|
||||
CyberMind — https://cybermind.fr
|
||||
|
||||
sbxwaf (Go) lit /etc/secubox/waf/on-demand-vhosts.json : l'ensemble des vhosts
|
||||
sleepables (portal_domain d'un module eager/on-demand). Pour une requête vers
|
||||
un de ces vhosts sans route active, sbxwaf proxy vers le waker au lieu de
|
||||
retourner 421. Remplace le nginx-sync de la Tâche 7 (pivot d'architecture :
|
||||
le déclencheur de réveil est sbxwaf, pas nginx).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
from .lifecycle import effective_lifecycle
|
||||
from .manifest import Manifest
|
||||
|
||||
|
||||
def ondemand_vhosts(manifests: dict[str, Manifest]) -> list[str]:
|
||||
"""Domaines portail (triés) des modules sleepables (eager/on-demand) et
|
||||
routés (portal_domain non nul). always-on/manual/sans-portail : exclus."""
|
||||
return sorted(
|
||||
m.portal_domain
|
||||
for m in manifests.values()
|
||||
if m.portal_domain is not None and effective_lifecycle(m) in ("eager", "on-demand")
|
||||
)
|
||||
|
||||
|
||||
def _write_atomic(path: Path, text: str) -> None:
|
||||
fd, tmp = tempfile.mkstemp(dir=path.parent, prefix=f".{path.name}.", suffix=".tmp")
|
||||
try:
|
||||
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||
f.write(text)
|
||||
os.replace(tmp, path)
|
||||
except BaseException:
|
||||
try:
|
||||
os.unlink(tmp)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
raise
|
||||
|
||||
|
||||
def write_ondemand(*, manifests: dict[str, Manifest], out_path: Path) -> list[str]:
|
||||
"""Écrit atomiquement la liste JSON des vhosts on-demand vers `out_path`,
|
||||
retourne la liste écrite."""
|
||||
out_path = Path(out_path)
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
doms = ondemand_vhosts(manifests)
|
||||
_write_atomic(out_path, json.dumps(doms))
|
||||
return doms
|
||||
|
|
@ -91,6 +91,8 @@ def main(argv: list[str] | None = None) -> int:
|
|||
sp.add_argument("--json", action="store_true")
|
||||
sp2 = sub.add_parser("nginx-sync")
|
||||
sp2.add_argument("--out", default="/etc/nginx/secubox-waker.d")
|
||||
sp3 = sub.add_parser("waf-sync")
|
||||
sp3.add_argument("--out", default="/etc/secubox/waf/on-demand-vhosts.json")
|
||||
args = p.parse_args(argv)
|
||||
if args.cmd == "nginx-sync":
|
||||
from . import nginxgen
|
||||
|
|
@ -98,6 +100,12 @@ def main(argv: list[str] | None = None) -> int:
|
|||
out_dir=Path(args.out))
|
||||
print(f"nginx-sync: {len(doms)} vhost(s) — {', '.join(doms) or 'aucun'}")
|
||||
return 0
|
||||
if args.cmd == "waf-sync":
|
||||
from . import wafsync
|
||||
doms = wafsync.write_ondemand(manifests=load_all(Path(args.root) / "modules.d"),
|
||||
out_path=Path(args.out))
|
||||
print(f"waf-sync: {len(doms)} vhost(s) — {', '.join(doms) or 'aucun'}")
|
||||
return 0
|
||||
if not _running_as_root():
|
||||
print("wake doit être lancé en root (il pilote systemd/LXC).", file=sys.stderr)
|
||||
return 1
|
||||
|
|
|
|||
32
packages/secubox-profiles/tests/test_wafsync.py
Normal file
32
packages/secubox-profiles/tests/test_wafsync.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# 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.
|
||||
|
||||
"""
|
||||
SecuBox-Deb :: profiles — tests génération liste on-demand-vhosts (sbxwaf wake trigger)
|
||||
CyberMind — https://cybermind.fr
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def test_ondemand_vhosts_selects_sleepable_portals():
|
||||
from api.wafsync import ondemand_vhosts
|
||||
from api.manifest import Manifest
|
||||
def m(mid, lc, dom):
|
||||
return Manifest(id=mid, category="infra", runtime="native", exposure="public",
|
||||
units=(f"{mid}.service",), portal_domain=dom, lifecycle=lc)
|
||||
ms = {"a": m("a","on-demand","a.gk2"), "b": m("b","always-on","b.gk2"),
|
||||
"c": m("c","eager","c.gk2"), "d": m("d","on-demand",None)}
|
||||
assert ondemand_vhosts(ms) == ["a.gk2", "c.gk2"] # sorted; b=always-on, d=no portal
|
||||
|
||||
def test_write_ondemand_atomic(tmp_path):
|
||||
from api.wafsync import write_ondemand
|
||||
from api.manifest import Manifest
|
||||
import json
|
||||
ms = {"a": Manifest(id="a", category="infra", runtime="native", exposure="public",
|
||||
units=("a.service",), portal_domain="a.gk2", lifecycle="on-demand")}
|
||||
out = tmp_path / "on-demand-vhosts.json"
|
||||
assert write_ondemand(manifests=ms, out_path=out) == ["a.gk2"]
|
||||
assert json.loads(out.read_text()) == ["a.gk2"]
|
||||
assert list(tmp_path.glob("*.tmp")) == []
|
||||
|
|
@ -109,3 +109,19 @@ def test_main_nginx_sync_writes_snippet_without_root(tmp_path):
|
|||
rc = w.main(["--root", str(tmp_path), "nginx-sync", "--out", str(out)])
|
||||
assert rc == 0
|
||||
assert (out / "demo.gk2.waker.conf").exists()
|
||||
|
||||
|
||||
def test_main_waf_sync_writes_json_without_root(tmp_path):
|
||||
# waf-sync est un aller-retour config->fichier, pas un pilotage
|
||||
# systemd/LXC : il ne doit pas exiger root (comme nginx-sync).
|
||||
import api.wake as w
|
||||
import json
|
||||
(tmp_path / "modules.d").mkdir()
|
||||
(tmp_path / "modules.d" / "demo.toml").write_text(
|
||||
'id="demo"\ncategory="infra"\nruntime="native"\nexposure="public"\n'
|
||||
'units=["demo.service"]\nlifecycle="on-demand"\n'
|
||||
'portal.domain="demo.gk2"\n')
|
||||
out = tmp_path / "on-demand-vhosts.json"
|
||||
rc = w.main(["--root", str(tmp_path), "waf-sync", "--out", str(out)])
|
||||
assert rc == 0
|
||||
assert json.loads(out.read_text()) == ["demo.gk2"]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user