wg-easy + Traefik docker-compose stack (Phase 1) plus a stdlib-only control-plane API for box registration, WireGuard peer provisioning via wg-easy, and per-box route publish/unpublish backed by Traefik's file provider (Phase 2, single-tenant mode). SQLite holds accounts/boxes/routes so a later multi-tenant shared instance is the same schema with more rows, not a reshape. wg-easy's actual REST API was verified against its source rather than assumed: it has no bearer-token auth (session-cookie login via POST /api/auth/password) and no way to accept an externally-generated public key (it always mints the keypair itself, private key included) — both corrected from the original plan during implementation.
54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
from control_plane import paths, reconciler, routes, traefikconf
|
|
from control_plane.db import Database
|
|
|
|
|
|
def _seeded_db(tmp_path):
|
|
db = Database(db_path=tmp_path / "gateway.db")
|
|
db.execute(
|
|
"INSERT INTO accounts (id, email, created_at, registration_token_hash, "
|
|
"account_token_hash, box_limit, route_limit_per_box) "
|
|
"VALUES ('acc1', NULL, '2026-01-01T00:00:00', '', '', 5, 5)"
|
|
)
|
|
db.execute(
|
|
"INSERT INTO boxes (id, account_id, name, wg_public_key, wg_peer_id, "
|
|
"wg_allowed_ip, box_token_hash, registered_at) "
|
|
"VALUES ('box1', 'acc1', 'my-box', 'pk==', 'peer-1', '10.8.0.5/32', 'h', "
|
|
"'2026-01-01T00:00:00')"
|
|
)
|
|
return db
|
|
|
|
|
|
def test_reconcile_recreates_missing_file(tmp_path, gateway_paths):
|
|
db = _seeded_db(tmp_path)
|
|
result = routes.publish_route(
|
|
db, "box1", "acc1", "vaultwarden", "vault.example.com", 8081, 5, "le"
|
|
)
|
|
route_file = paths.dynamic_dir() / f"route-{result['route_id']}.yml"
|
|
route_file.unlink()
|
|
assert not route_file.exists()
|
|
|
|
reconciler.reconcile(db, cert_resolver="le")
|
|
|
|
assert route_file.exists()
|
|
assert "vault.example.com" in route_file.read_text()
|
|
|
|
|
|
def test_reconcile_removes_orphan_file(tmp_path, gateway_paths):
|
|
db = _seeded_db(tmp_path)
|
|
traefikconf.write_route("orphan", "orphan.example.com", "10.8.0.9", 80, cert_resolver="le")
|
|
|
|
reconciler.reconcile(db, cert_resolver="le")
|
|
|
|
assert "orphan" not in traefikconf.existing_route_ids()
|
|
|
|
|
|
def test_reconcile_ignores_disabled_routes(tmp_path, gateway_paths):
|
|
db = _seeded_db(tmp_path)
|
|
result = routes.publish_route(
|
|
db, "box1", "acc1", "vaultwarden", "vault.example.com", 8081, 5, "le"
|
|
)
|
|
db.execute("UPDATE routes SET enabled = 0 WHERE id = ?", (result["route_id"],))
|
|
|
|
reconciler.reconcile(db, cert_resolver="le")
|
|
|
|
assert traefikconf.existing_route_ids() == set()
|