Lets the operator's Android / Linux / Windows / macOS devices dial in from anywhere and behave like LAN machines: full reach to 192.168.2.0/24 plus internet egress out the house WAN. Opposite direction to platform/vpn-egress, which sends pods OUT through PIA; the two share nothing but the word "VPN". WireGuard because it is the only option with a first-party client on all four named platforms, and because its handshake MAC is keyed with the responder's own public key: an unauthenticated packet gets NO reply, so udp/51820 is indistinguishable from a closed port. That, not "it is encrypted", is what makes an unfiltered WAN port defensible, and why there is no source allowlist. hostNetwork: true is REQUIRED, not convenient. A pod-networked server must SNAT clients to its own pod IP, so every remote client would carry that pod's Cilium identity -- and a pod-sourced request to the shared Gateway VIP returns 403 from Envoy for ALL 16 hostnames (measured 2026-10-04), because Cilium's Gateway preserves the original source identity and enforces the destination's ingress policy. The obvious build would have shipped a healthy-looking tunnel on which every cluster web UI was 403 and *.home.keithjackson.dev did not even resolve. In the host netns traffic is SNATed to the node and arrives as host/remote-node, exactly like a LAN client. It also sidesteps the unsafe ip_forward sysctl, the L2-announcement lease lottery, and double encapsulation (hence MTU 1420). Permitted by restrict-host-namespaces' own carve-out for platform namespaces. The cost is stated in networkpolicy.yaml rather than papered over: no NetworkPolicy constrains a hostNetwork pod. A DaemonSet so the hand-made WAN forward cannot be invalidated by a reschedule; all three nodes answer the same keys, so failover is a one-line router change. Peer PUBLIC keys are plaintext in peers.yaml, which makes "who can get into my house" a reviewable PR diff. The only secret -- the server's private key -- is an out-of-band Secret: an agent must not mint a long-lived private key, and the two halves are joined only at pod start into a tmpfs that dies with the pod. Ships INERT-BUT-HEALTHY with zero peers. THREE off-repo manual steps remain (kernel module, server key, WAN forward) -- runbooks/vpn-remote-access.md. Touches clusters/** , so ci/approval-policy.sh will block pending the operator's /approve-override. Deliberately not posted by me (CLAUDE.md §1). Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
57 lines
3.0 KiB
YAML
57 lines
3.0 KiB
YAML
---
|
|
# =================================================================================================
|
|
# READ THIS BEFORE TRUSTING THIS FILE: it does NOT constrain the WireGuard listener.
|
|
# =================================================================================================
|
|
# Every other platform namespace in this repo (crafty, headlamp, homepage, runbooks) ships an
|
|
# explicit default-deny baseline as defense in depth, because Kyverno's generator only covers
|
|
# `app-namespace=true` namespaces. This file keeps that convention — but it would be dishonest to
|
|
# leave it at that, because the convention does not actually bite here:
|
|
#
|
|
# THE `wireguard` DaemonSet RUNS WITH hostNetwork: true, SO IT HAS NO CILIUM ENDPOINT, AND NO
|
|
# NetworkPolicy OR CiliumNetworkPolicy APPLIES TO IT — NEITHER THIS ONE NOR ANY OTHER.
|
|
#
|
|
# That is the documented reason restrict-host-namespaces forbids hostNetwork in app namespaces at
|
|
# all ("a pod on hostNetwork bypasses the CNI (Cilium) datapath and therefore ESCAPES the
|
|
# default-deny NetworkPolicy fabric -- it is the most direct way to defeat R-NET-01"). Here that
|
|
# escape is the deliberate, required design (daemonset.yaml explains why: without it, remote
|
|
# clients carry a pod identity and are 403'd by every Gateway hostname). A CiliumNetworkPolicy
|
|
# admitting `world` to udp/51820 would therefore be theatre: it would read as the control that
|
|
# protects this port, and it would be enforcing nothing. So there isn't one.
|
|
#
|
|
# WHAT ACTUALLY PROTECTS THE EXPOSED PORT, then, since the CNI does not:
|
|
# 1. WireGuard's cryptographic peer authentication. A packet whose key is not in peers.yaml gets
|
|
# NO REPLY — see README "Why this is safe to expose". This is the real control.
|
|
# 2. The SOHO router's forward: only udp/51820 is forwarded inbound, to one node.
|
|
# 3. The blast radius once authenticated is deliberately the whole LAN — that IS the requirement
|
|
# ("clients should reach the entire local network"). README "What a client can reach" states
|
|
# the consequence in full, including reach to the nodes' own control plane, and gives the
|
|
# narrowing recipe for an operator who wants less than that.
|
|
#
|
|
# WHAT THIS FILE IS STILL FOR: it is the namespace's standing posture for any FUTURE pod-networked
|
|
# workload added here (a metrics exporter is the likely one — README "Not built"). Without it, such
|
|
# a pod would be born unrestricted, because Kyverno will not generate a baseline in a namespace
|
|
# that is not labelled `app-namespace=true`, and this one deliberately is not.
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: NetworkPolicy
|
|
metadata:
|
|
name: default-deny
|
|
namespace: vpn-ingress
|
|
labels:
|
|
homelab.keithjackson.dev/baseline: default-deny
|
|
spec:
|
|
podSelector: {}
|
|
policyTypes:
|
|
- Ingress
|
|
- Egress
|
|
ingress: []
|
|
egress:
|
|
- to:
|
|
- namespaceSelector:
|
|
matchLabels:
|
|
kubernetes.io/metadata.name: kube-system
|
|
ports:
|
|
- protocol: UDP
|
|
port: 53
|
|
- protocol: TCP
|
|
port: 53
|