From 6e250017385ecbfe8fc9f7e5dde89740c3c526c0 Mon Sep 17 00:00:00 2001 From: Hermes Date: Sat, 3 Oct 2026 21:25:17 -0700 Subject: [PATCH] P7 spec rev3: X25519-only browser keygen (no ECDSA fallback), unblock row + honest semantics everywhere, three-zone WG firewall model --- BACKLOG.md | 11 +++++++---- specs/PRIVATE-NETWORK-SPEC.md | 22 +++++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/BACKLOG.md b/BACKLOG.md index abb2ffb..4af6a72 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -475,8 +475,9 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami `/network/devices/:id/revoke`. Orchestrates: provider calls, session kill (existing auth store), access-map purge/restore, audit log entries. Block must be fully reversible (snapshots pre-block grants). Remove deletes devices + - person record. Tests: orchestration order (mocked provider), reversibility, - last-admin guard integration, audit entries written. + person record. Tests: orchestration order (mocked provider), honest unblock + semantics (Tailscale: reauthRequired true + note; WireGuard: immediate + restore), last-admin guard integration, audit entries written. **Note (not a ticket):** the access map module itself is DC-145. @@ -547,8 +548,10 @@ Tickets DC-046 through DC-049 implement pluggable auth + email magic link. Sami - **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), baseline: dcnet0 input = WG UDP + ICMP from 10.53.0.0/16 + - per-resource ports; host admin ports restricted to admin peers. Tests: ruleset + rollback file), three-zone baseline: external-interface UDP listen-port accept + (NOT on dcnet0); dcnet0 local input = ICMP + admin ports from admin set only + + resource ports; dcnet0 forwarding = per-resource sets, peer-to-peer default + drop. 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 ec2a7fa..50a5610 100644 --- a/specs/PRIVATE-NETWORK-SPEC.md +++ b/specs/PRIVATE-NETWORK-SPEC.md @@ -19,6 +19,7 @@ Two network modes, one product: | Control plane | api.tailscale.com (OAuth client preferred; API key fallback) | DashCaddy itself (host wg-quick + key store) | | Invites | Tailscale user-invites API, link-first (no email required) | DashCaddy invite link + QR config | | Block/remove | API device expire/delete + ACL deny | Remove peer from hub + firewall rule | +| Unblock | Re-allow at ACL level; expired device keys must re-authenticate (see §5) | Re-add peer from stored record (immediate) | | Rules | Tailnet policy ACLs compiled by DashCaddy | Hub firewall (nftables set-based) compiled by DashCaddy | | Outside dependency | Yes | None | @@ -164,15 +165,26 @@ Builds on the existing DashCA pipeline (routes/ca.js, DC-076). Changes: 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. +- Firewall baseline — three distinct zones, never conflated: + (a) **External input**: the WireGuard UDP listen port arrives on the EXTERNAL + interface (e.g. eth0), not dcnet0 — its accept rule is scoped to the external + interface name (captured at setup). + (b) **dcnet0 local input** (iifname dcnet0): ICMP from 10.53.0.0/16, admin + ports (API/SSH on 10.53.0.1) from admin peers only, per-resource ports to + 10.53.0.1; drop other new inbound to 10.53.0.1. + (c) **dcnet0 forwarding** (ip_forward=1): peer→resource gated by per-resource + sets; peer→peer default DROP (both iifname and oifname dcnet0 tails) unless a + grant implies it. ### 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). + using WebCrypto **X25519** — WireGuard requires Curve25519 key material; there + is NO fallback algorithm (a browser lacking X25519 support gets an explicit + "browser not supported" error, not a degraded key). Raw key serialization: + the browser exports the 32-byte raw key, base64-encoded (standard WireGuard + private-key format, `wg genkey`-compatible). The server receives the base64 + public key (44 chars ending '=') and validates length/charset before use. - 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).