"""De apps waarmee je een machine met een ander netwerk verbindt. Deze vier hebben iets gemeen dat je in een formulier niet ziet: ze werken alleen met rechten die een gewone container niet heeft (NET_ADMIN, /dev/net/tun), of ze vragen een eenmalige handeling in de terminal. Zonder dat staat er een app die start en niets doet. """ import json import sys import tempfile from pathlib import Path import pytest import yaml WORTEL = Path(__file__).resolve().parent.parent sys.path.insert(0, str(WORTEL / "server-up")) from core import boilerplates as bp VERBINDINGSAPPS = ["cloudflared", "protonmail-bridge", "tailscale", "wireguard"] def _tpl(app: str) -> dict: return json.loads((WORTEL / "apps" / app / "template.json").read_text("utf-8")) def _render(app: str) -> dict: pad = WORTEL / "apps" / app velden = bp.fields(pad) waarden = {f["name"]: f["default"] for f in velden if f.get("default") is not None} for f in velden: if f.get("required") and not waarden.get(f["name"]): waarden[f["name"]] = "testwaarde12345678901234567890ab" with tempfile.TemporaryDirectory() as t: bp.render_to_dir(pad, Path(t), waarden) return yaml.safe_load((Path(t) / "docker-compose.yml").read_text()) @pytest.mark.parametrize("app", VERBINDINGSAPPS) def test_staan_in_de_catalogus_en_draaien_op_een_pi(app): meta = _tpl(app)["metadata"] assert {"amd64", "arm64"} <= set(meta["architectures"]), \ "juist op een Pi wil je een tunnel draaien" assert "netwerk" in meta["categories"] def test_wireguard_krijgt_de_rechten_die_een_tunnel_nodig_heeft(): """Zonder NET_ADMIN en de kernelmodules start wg-quick niet, en zonder src_valid_mark valt het verkeer stil zodra alles door de tunnel gaat.""" svc = _render("wireguard")["services"]["wireguard"] assert "NET_ADMIN" in svc["cap_add"] assert "SYS_MODULE" in svc["cap_add"] assert "/lib/modules:/lib/modules:ro" in svc["volumes"] assert "net.ipv4.conf.all.src_valid_mark=1" in svc["sysctls"] assert any(str(p).endswith("51820/udp") for p in svc["ports"]) def test_wireguard_legt_uit_waar_je_eigen_configuratie_heen_moet(): """De clientrol is de reden dat deze app er is; die werkt alleen als je weet dat je wg0.conf in wg_confs hoort.""" bron = (WORTEL / "apps" / "wireguard" / "files" / "compose.yaml").read_text("utf-8") assert "wg_confs" in bron velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "wireguard")} assert "wg_confs" in velden["appdata_dir"]["description"] def test_tailscale_draait_op_de_host_en_onthoudt_zijn_aanmelding(): """In een eigen netwerknamespace bereikt de tunnel alleen die container; en zonder bewaarde staat moet je na elke herstart opnieuw koppelen.""" svc = _render("tailscale")["services"]["tailscale"] assert svc["network_mode"] == "host" assert "NET_ADMIN" in svc["cap_add"] assert any("/var/lib/tailscale" in v for v in svc["volumes"]) assert "/dev/net/tun:/dev/net/tun" in svc["volumes"] def test_tailscale_vraagt_om_een_auth_key_als_geheim(): velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "tailscale")} assert velden["auth_key"]["secret"] is True assert velden["auth_key"]["required"] is True # De routes zijn optioneel: de meeste mensen willen alleen deze machine. assert velden["routes"]["required"] is False def test_cloudflared_zet_geen_poort_open(): """Dat is het hele punt van een tunnel; publiceerde hij poorten, dan kon je net zo goed een reverse proxy nemen.""" svc = _render("cloudflared")["services"]["cloudflared"] assert "ports" not in svc assert "--no-autoupdate" in svc["command"], \ "een container die zichzelf bijwerkt, wijkt af van wat je uitrolde" velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "cloudflared")} assert velden["tunnel_token"]["secret"] is True def test_protonmail_bridge_botst_niet_met_mailpit(): """Allebei praten ze SMTP en je kunt ze prima naast elkaar draaien: de een vangt testmail op, de ander verstuurt echte mail.""" velden = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "protonmail-bridge")} mailpit = {f["name"]: f for f in bp.fields(WORTEL / "apps" / "mailpit")} smtp_poorten = {f["default"] for f in mailpit.values() if f.get("type") == "int"} assert velden["port_smtp"]["default"] not in smtp_poorten def test_protonmail_bridge_vertelt_over_de_eenmalige_aanmelding(): """Die kan niet via een formulier: Proton vraagt om een tweede factor. Staat het er niet bij, dan draait de bridge zonder account en snapt niemand waarom er geen mail doorkomt.""" meta = _tpl("protonmail-bridge")["metadata"] bron = (WORTEL / "apps" / "protonmail-bridge" / "files" / "compose.yaml").read_text("utf-8") for tekst in (meta["architecture_note"], bron): assert "--cli" in tekst assert "docker exec" in meta["architecture_note"]