fix: critical bugs + security hardening

Phase 1 (critical bugs):
- Fix firewall import string-to-list bug (system_import.py)
- Add rich rules removal in firewall config apply (handlers/firewall.py)

Phase 2 (security hardening):
- Restrict sudo wildcards to specific paths (sudoers.d/vacuum-walld)
- Fix TOCTOU: use /run/vacuum-wall/ for temp files (nginx, dnsmasq, network handlers)
- Remove unnecessary sudo from wg genkey/pubkey (handlers/wireguard.py)

Phase 3 (validation):
- Validate poll intervals > 0 (daemon/server.py)
- Restrict sysctl to whitelisted parameters (handlers/network.py)

Phase 4 (defensive programming):
- Enforce shell=False in run() and run_proc() (lib/common.py)
- Track issuance tasks for graceful shutdown (handlers/acme.py)
- Add nginx template marker consistency tests (tests/test_system_import.py)
This commit is contained in:
2026-07-10 17:05:37 +00:00
parent 803258cf18
commit 05524f3756
27 changed files with 1805 additions and 521 deletions
+24 -2
View File
@@ -43,16 +43,33 @@ logger = logging.getLogger(__name__)
PROJECT_DIR = Path(__file__).resolve().parent.parent.parent
CONFIG_DIR = PROJECT_DIR / "config" / "network"
DATA_DIR = PROJECT_DIR / "data" / "networkd"
RUNTIME_DIR = Path("/run/vacuum-wall")
_ALLOWED_SYSCTL_KEYS: set[str] = {
"net.ipv4.ip_forward",
"net.ipv4.conf.all.forwarding",
"net.ipv4.conf.all.accept_redirects",
"net.ipv4.conf.default.accept_redirects",
"net.ipv4.conf.all.send_redirects",
"net.ipv4.conf.default.send_redirects",
"net.ipv4.conf.all.rp_filter",
"net.ipv4.icmp_echo_ignore_all",
"net.ipv4.tcp_syncookies",
}
def _copy_and_reload(iface_name: str) -> None:
"""Copy generated 99-<name>.network file to /etc/systemd/network/ and reload."""
validate_interface_name(iface_name)
src = DATA_DIR / f"99-{iface_name}.network"
runtime_src = RUNTIME_DIR / f"99-{iface_name}.network"
dst_dir = Path("/etc/systemd/network")
RUNTIME_DIR.mkdir(exist_ok=True)
runtime_src.write_text(src.read_text())
run(["mkdir", "-p", str(dst_dir)], sudo=True)
dst = dst_dir / f"99-{iface_name}.network"
run(["cp", str(src), str(dst)], sudo=True)
run(["cp", "--", str(runtime_src), str(dst)], sudo=True)
runtime_src.unlink(missing_ok=True)
# Remove lower-priority .network files that match this interface
# (they would override our config due to higher systemd priority)
@@ -253,9 +270,12 @@ def apply_all(_request: Any, _body: Any) -> dict[str, Any]:
cleaned.append(f)
for f in generated:
tmp = RUNTIME_DIR / f.name
run(["cp", "--", str(f), str(tmp)], sudo=False)
dst = sys_dir / f.name
run(["mkdir", "-p", str(sys_dir)], sudo=True)
run(["cp", str(f), str(dst)], sudo=True)
run(["cp", "--", str(tmp), str(dst)], sudo=True)
tmp.unlink(missing_ok=True)
_full_reload()
@@ -325,6 +345,8 @@ def set_sysctl(_request: Any, body: dict[str, Any] | None) -> dict[str, Any]:
raise ValueError("'name' is required")
if not re.match(r"^[a-zA-Z0-9_]+(\.[a-zA-Z0-9_]+)*$", name):
raise ValueError("'name' is not a valid sysctl key")
if name not in _ALLOWED_SYSCTL_KEYS:
raise ValueError("'name' is not a permitted sysctl key")
value = str(body.get("value", "")).strip()
if not value:
raise ValueError("'value' is required")