The operator ruled the implementation repo's main (50c8719) the latest and accepted state. Sync the spec to it. Accepted changes are reflected: one bare-metal environment, TrueNAS bulk storage, R2 off-cluster backup, CloudNativePG, single-node VPN gateway with Cilium fail-closed, inbound WireGuard, per-app auth sidecars, Renovate, the CI gate as approver, and the pm-agent. New ADR-0017..0025; ADR-0016 corrected and accepted; R-* IDs unchanged, eight amended. Where the implementation has simply not met a requirement, the requirement stands and the shortfall is recorded in docs/16-known-gaps.md. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4.4 KiB
ADR-0021 — Inbound remote access: WireGuard on a hostNetwork DaemonSet
- Status: Accepted
- Date: 2026-11-08
- Accepted by: operator ruling of 2026-11-08 that the implementation is the accepted state; effective on merge.
- Serves: remote operator access (no dedicated requirement ID); bounds R-NET-01, R-SEC-02, R-SEC-04
- Supersedes: —
Context
The operator's Android, Linux, Windows and other devices need to reach the home network from anywhere
as though on the LAN. Several services are LAN/VPN-only by design (ADR-0016), and the Kubernetes API is
LAN/VPN-only (R-SEC-04), so the "VPN" in that phrase needs a defined mechanism. This is unrelated to
vpn-class egress (ADR-0015, ADR-0020), which sends pods out through a provider tunnel.
Two facts about this cluster shape the design:
- Cilium decides what a packet may reach from the security identity of its source. Measured: a request to the shared Gateway from inside a pod is refused for every Gateway hostname. A pod-networked VPN server would give every client that pod's identity: a healthy tunnel on which every web UI is refused.
- The SOHO edge has no automation and cannot hairpin NAT. An inbound forward is a manual step that names one node address.
Decision
Remote access is WireGuard on udp/51820, run as a Flux-managed DaemonSet in the host network namespace on every node.
- Host network so that clients never carry a pod identity.
- DaemonSet so that every node holds an identical warm listener, and losing the forwarded node is a one-line router change with no cluster work and no new client profiles.
- Authorization is a reviewable git diff: peer public keys are in plain text in git, so granting or revoking a device is a reviewed change that rolls the listeners. The server's private key is SOPS-encrypted (ADR-0007), and joined with the peer list only in memory at pod start.
- Reach equals a LAN machine: the whole LAN, the Gateway, Pi-hole, and the internet through the
house WAN. Clients are
internetclass, never steered to the provider tunnel. - Endpoint: the DNS-only
wan.record (ADR-0016). A client inside the house cannot use it, as there is no hairpin, and uses a node's LAN address instead.
This is a deliberate exception to default-deny (R-NET-01). A host-network workload has no Cilium endpoint, so no network policy applies to it. The exception is scoped to this platform component; application namespaces remain barred from the host network by admission policy. The boundary is WireGuard's peer authentication: a packet from an unknown key gets no reply, so the port is indistinguishable from a closed one.
Alternatives considered
- OpenVPN — rejected: a CA, per-device certificates and a revocation list are long-lived state to protect and rotate.
- IKEv2/IPsec — rejected: painful to containerize and brittle behind double NAT.
- Pod-networked WireGuard — rejected: clients inherit the pod's identity and every Gateway hostname refuses them, while the tunnel reports healthy.
- A LoadBalancer VIP for the listener — rejected: adds an L2-announcement lease, SNAT and a node-to-node hop to the one path whose failure means the operator is locked out while away.
- Single-replica Deployment — rejected: a reschedule silently invalidates the router forward.
- Carry it over the Cloudflare Tunnel — not possible: the tunnel carries HTTP(S), and WireGuard is UDP to a node.
- Encrypt the peer list / add preshared keys — rejected: hides the change a reviewer most needs to see, and a preshared key would be a further secret for a threat not in the model.
Consequences
- No network policy constrains this workload. Peer authentication and the single forwarded port are the controls.
- An enrolled device is as privileged as a machine in the house, including reach to the nodes' control-plane ports. A stolen device is that device until revoked, so revocation must be prompt. Narrowing is possible per profile (split tunnel) or by host firewall rules in the listener.
- The WAN IP stays public for this port (ADR-0016); the forward is a manual edge step.
- The tunnel carries IPv4 only. Client profiles default to a conservative MTU, because a client's own path can be narrower than the server's.
- No per-peer metrics exist; a WireGuard exporter would need a second privileged container beside the internet-facing tunnel.