From 2e6d5352424a4f48d739afc819443a8792e54e55 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 3 Oct 2026 21:14:30 -0700 Subject: [PATCH] P7 spec rev2: honest unblock semantics, WG addressing/keygen model (browser-side keygen, CIDR AllowedIPs), ticket-set fixes --- BACKLOG.md | 28 ++++++++----- specs/PRIVATE-NETWORK-SPEC.md | 75 +++++++++++++++++++++++++++++------ 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index 5df898e..abb2ffb 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -478,7 +478,7 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami person record. Tests: orchestration order (mocked provider), reversibility, last-admin guard integration, audit entries written. -### DC-143 cleanup note: access map module itself is DC-145. +**Note (not a ticket):** the access map module itself is DC-145. ### DC-144: Groups (DashCaddy-side) + resources - **status:** todo @@ -525,24 +525,30 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami ### DC-149: WireGuard provider — hub management - **status:** todo -- **details:** `providers/wireguard.js`: wg-quick iface `dcnet0`, address pool - 10.d.c.0.0/16 stable assignment, keygen on hub, peer store, systemd unit - if missing. All execFile-mocked in tests. Tests: pool assignment stability, - peer add/remove, unit generation, error paths (missing wg binary). +- **details:** `providers/wireguard.js`: wg-quick iface `dcnet0` at 10.53.0.1/16 + (client pool 10.53.0.0/16, one /32 per device, stable assignment), HUB keypair + generated host-side, peer store (pubkeys only), systemd unit if missing, + net.ipv4.ip_forward=1 for hub-and-spoke, no NAT on dcnet0. All execFile-mocked + in tests. Tests: pool assignment stability, peer add/remove, unit generation, + error paths (missing wg binary). ### DC-150: WireGuard invites (QR) + client config - **status:** todo -- **details:** one-time token (7d TTL, single-use) → redemption page rendering - client config + QR (qrencode -t ansiutf8 → pre? no: PNG served) with Address, - DNS=Technitium hub IP, AllowedIPs = private zone only (split tunnel). Private - key shown once, never stored server-side. Tests: token lifecycle, config - template correctness (allowed-ips, DNS), single-use enforcement, expiry. +- **details:** one-time token (7d TTL, single-use) → redemption page. Client + keypair generated IN THE BROWSER (WebCrypto X25519, ECDSA-P256 fallback); + server receives the public key only, registers the peer /32, returns config + (Address, DNS=10.53.0.1, hub PublicKey, AllowedIPs=10.53.0.0/16 — a CIDR, + never a DNS zone). Config + QR rendered client-side; private key never + transits or rests on the server. Tests: token lifecycle, single-use + enforcement, expiry, server-rejects-private-key-posts, config template + correctness. ### DC-151: WireGuard firewall matrix (nftables) - **status:** todo - **details:** per-resource nft sets (net_dc_res_), peer IPs per grant, transactional apply (build temp ruleset → `nft -c` → `nft -f` → verify → - rollback file), host admin ports restricted to admin peers. Tests: ruleset + rollback file), baseline: dcnet0 input = WG UDP + ICMP from 10.53.0.0/16 + + per-resource ports; host admin ports restricted to admin peers. Tests: ruleset golden text, transactional failure injection (nft -c fails → no apply), verify-step failure, admin-port isolation. diff --git a/specs/PRIVATE-NETWORK-SPEC.md b/specs/PRIVATE-NETWORK-SPEC.md index c92189c..ec2a7fa 100644 --- a/specs/PRIVATE-NETWORK-SPEC.md +++ b/specs/PRIVATE-NETWORK-SPEC.md @@ -110,8 +110,13 @@ working as-is; the new `routes/network.js` + `src/network/` are additive. - Invite = link-first (DC-085 pattern): inviteUrl is the artifact, email is an opt-in checkbox, never logged raw to console. -- Block (reversible): expire device keys + deny at network layer + kill - DashCaddy sessions + mark person blocked. Unblock reverses all four. +- Block: network-layer deny (ACL / firewall) + expire Tailscale device keys + kill + DashCaddy sessions + mark person blocked. **Unblock honestly:** the deny, the + status, and the session policy reverse — but expired Tailscale device keys are + NOT un-expired; those devices must re-authenticate with Tailscale on next + connect. The UI says "blocked devices will need to sign in again" rather than + pretending instant reversal. WireGuard peers restore immediately on re-add + (peer config is re-generated server-side from the stored pubkey). - Remove: block semantics + delete provider devices + delete person record + purge from access map. The last active admin can never be blocked/removed (same guard as the ACL compiler — one shared `assertNotLastAdmin()`). @@ -145,20 +150,64 @@ Builds on the existing DashCA pipeline (routes/ca.js, DC-076). Changes: ## 8. WireGuard provider (DC-149–151) -- Hub: `wg-quick` interface `dcnet0` managed by DashCaddy ( systemd unit - `dcnet-hub.service` if absent ), 10.D.C.0.0/16 address pool with stable - per-device assignment stored in people.json. -- Keygen on the hub; peer private keys exist only in the one-time invite - redemption (client config + QR). Server stores pubkeys only. -- Client config template: Address, DNS = Technitium hub IP, - AllowedIPs = private zone subnet only (split tunnel — normal internet - traffic never touches the hub). -- Invite redemption: one-time token → render config + QR (qrencode), page - auto-expires; token single-use, 7-day TTL. -- Firewall: nftables matrix (above). Server resources listen on the hub IP; +### 8.1 Addressing and routing (exact prefixes) + +- Hub interface `dcnet0`: 10.53.0.1/16, listen port chosen at setup (default 51820, + UDP). Endpoint = host's reachable address (setup captures it; wizard warns if + it's behind NAT without a forwarded port). +- Client address pool: 10.53.0.0/16, one /32 per device (10.53.x.y/32 assigned + on device add, stable, stored in people.json devices[]). +- **AllowedIPs = 10.53.0.0/16 only** (a CIDR, never a DNS zone). Split tunnel: + client's other traffic is untouched. Resource names resolve via Technitium + (8.3) to IPs inside 10.53.0.0/16, which routes through the tunnel. +- Hub forwarding: `net.ipv4.ip_forward=1` is required for hub-and-spoke + peer→peer traffic; peer→hub-resource traffic is local. No NAT/masquerade on + dcnet0 — all resource access is to hub-local services on 10.53.0.1 or to + other peers' /32s, so addresses stay routable and firewall rules match peers. +- Firewall baseline: dcnet0 input allows only WireGuard UDP (listen port), + ICMP from 10.53.0.0/16, and the per-resource ports from 10.53.0.0/16; + admin ports (API/SSH) on 10.53.0.1 are restricted to admin peers only. + +### 8.2 Key lifecycle (no server-side private-key storage) + +- **Client keypairs are generated in the invitee's browser** at redemption time + (WebCrypto: X25519 where available, fallback ECDSA-P256; wireguard-tools + accepts any interface config — DashCaddy consumes the resulting public key). +- The redemption page posts ONLY the public key to the server; the server + registers it as a WireGuard peer (10.53.x.y/32) and returns the client + config (Address, DNS, PublicKey of hub, AllowedIPs = 10.53.0.0/16). +- The private key NEVER transits or rests on the server: it lives in the + browser page for the session, rendered into the config + QR client-side. +- Server stores: peer public keys, assigned /32s, names — nothing else. +- Revocation = remove peer (pubkey) from the hub + firewall-set update; the + client's private key becomes useless without hub acceptance. + +### 8.3 DNS + +- Technitium serves the private zone on the hub IP (10.53.0.1). Client config + DNS = 10.53.0.1. Per-resource/per-device records resolve to 10.53.x.y /32s + (per DC-146). In Tailscale mode, the same records hold tailnet IPs instead. + +### 8.4 Hub management + +- `wg-quick` interface `dcnet0` managed by DashCaddy (systemd unit + `dcnet-hub.service` if absent), keygen for the HUB keypair on the host + (hub key is host-local and backed up with it). + +### 8.5 Invites + +- Invite redemption: one-time token (7-day TTL, single-use) → redemption page + (browser keygen per 8.2) → config + QR rendered client-side, page + auto-expires after redemption. + +- Firewall: nftables matrix (section 4). Server resources listen on the hub IP; per-resource sets gate peer access. The host's own admin ports are NOT exposed on dcnet0 except to admins. +WireGuard mode dependency note: "no third party" still depends on host +networking — a reachable UDP endpoint, wireguard-tools, nftables, and Technitium +on the DashCaddy host. + ## 9. Setup wizard (DC-152) Single flow, mode pick first: