diff --git a/PLAN.md b/PLAN.md index fe99088..b0fcee3 100644 --- a/PLAN.md +++ b/PLAN.md @@ -145,6 +145,8 @@ Needed to replace the user's hand-written router (host `antigua`, dnsmasq). Serv ## Replacing `antigua` (the user's hand-written router) +> `examples/home` (the hand-written Nix port of antigua's router role), its `home` check, `installer-home` and the WireGuard import script were removed once the installer and web UI could configure the router (`settings.json`); the notes below describe that port and stay for the reasoning. The PPPoE username and everything else now live in `settings.json` (Network page). + Source: the user's infra repo (host `antigua`, NixOS, dnsmasq + networkd + pppd). It is also a k3s/Docker/Gitea host today; **those services will stay on antigua / move elsewhere, so the router does not need input rules for them or trusted container interfaces.** The new box is set up next to antigua and tested by swapping the two. Done: PPPoE on VLAN 1001 over the shared NIC (`eno1`, LAN untagged), DNS records/domain/forward zones/DHCP reservations/DNS for site-to-site peers, WireGuard (freifunk dial-out with masquerade, family site-to-site with PSK, mobile-clients road warrior), MSS clamp. diff --git a/examples/home/default.nix b/examples/home/default.nix deleted file mode 100644 index 52f317c..0000000 --- a/examples/home/default.nix +++ /dev/null @@ -1,213 +0,0 @@ -# The home gateway that replaces antigua's routing role (ported from antigua's -# hand-written networkd/pppd/dnsmasq/WireGuard configuration). -# -# ISP -- switch port --+-- enp2s0 (untagged) LAN 192.168.168.0/24 (+ ULA, + delegated /64) -# +-- enp2s0.1001 PPPoE session: IPv4 and IPv6 (IPv6CP) -# -# Secrets are NOT in this repository. Put them in the directory handed to -# `router-install --secrets`; it lands in /var/lib/router on the box: -# -# pppoe-password the PPPoE password (from the ISP) -# cloudflare-ddns-token Cloudflare API token for ddclient -# wireguard/freifunk.key private keys (the ones antigua uses today keep the -# wireguard/family.key peers working without any change on their side; -# wireguard/family.psk never run both boxes with the same keys at once) -# wireguard/mobile-clients.key -# wireguard/mobile-clients.psk -# -# Before installing: fill in the PPPoE username below. -{ config, lib, ... }: -let - secrets = "/var/lib/router"; - k8s = { - v4 = "10.43.162.50"; - v6 = "fd32:66ac:e01f::f294"; - }; -in -{ - imports = [ ../../modules/installer/layout.nix ]; - - networking.hostName = "gateway"; - time.timeZone = "Europe/Berlin"; - system.stateVersion = "26.11"; - - users.users.root.openssh.authorizedKeys.keys = [ - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHqyMLgAhEdnrRrd2CeYbphGDMPq/cBDjFJ4q4ItZusn nils@Nilss-Mac-mini.mch.stinnesbeck.com" - "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBFxbNNk87tvMxruuKvrryK7iAdf4xgS4xFqjKejNAI6 nils@nixtop" - ]; - - router = { - enable = true; - - wan = { - interface = "enp2s0"; - ipv4 = { - type = "pppoe"; - pppoe = { - vlan = 1001; - # From antigua's networking.nix (pppd `user`); kept out of this file. - username = "CHANGE-ME"; - passwordFile = "${secrets}/pppoe-password"; - }; - }; - # IPv6 runs inside the PPP session, with one delegated /64. - ipv6 = { - enable = true; - transport = "ppp"; - }; - }; - - lans.lan = { - interface = "enp2s0"; - internet = true; - ipv4.address = "192.168.168.254"; - ipv6 = { - pd = true; - address = "fd2c:cdef:cde9::1/64"; - }; - dhcp = { - pool = { - start = "192.168.168.100"; - end = "192.168.168.200"; - }; - reservations.truenas = { - mac = "a8:a1:59:0b:4b:97"; - address = "192.168.168.251"; - }; - }; - }; - - dns = { - domain = "mch.stinnesbeck.com"; - forwarders = [ - "1.1.1.1" - "2606:4700:4700::1111" - ]; - hosts = { - gateway = [ "192.168.168.254" ]; - "photos.stinnesbeck.com" = [ - k8s.v4 - k8s.v6 - ]; - "git.stinnesbeck.com" = [ - k8s.v4 - k8s.v6 - ]; - }; - overrides."k8s.stinnesbeck.com" = [ - k8s.v4 - k8s.v6 - ]; - forwardZones."tdf.stinnesbeck.com" = [ "10.1.0.254" ]; - }; - - wireguard = { - # Provider-style dial-out: only the treasure-maps.com hosts go through - # it (antigua accepts 0.0.0.0/0 but routes just these), masqueraded. The - # kill switch keeps them from leaking out of the WAN if the tunnel is down. - freifunk = { - privateKeyFile = "${secrets}/wireguard/freifunk.key"; - zone = "freifunk"; - masquerade = true; - addresses = [ - "10.255.1.8/32" - "fd80:3ea2:e399:203a::8/128" - ]; - peers.supernode = { - publicKey = "5B/YTaDPVWVApUyHshJp899iXXlBy8rBqJUpYvKo+1s="; - endpoint = "5.9.220.114:42001"; - persistentKeepalive = 15; - allowedIPs = [ - "185.90.196.65/32" - "185.90.196.97/32" - "185.90.196.129/32" - ]; - }; - }; - - # Site-to-site with Troisdorf; no transfer network. The site may use the - # router (SSH, DNS), and the router reaches it from its LAN addresses. - family = { - privateKeyFile = "${secrets}/wireguard/family.key"; - listenPort = 36432; - zone = "family"; - allowManagement = true; - dns = true; - sourceAddresses = [ - "192.168.168.254" - "fd2c:cdef:cde9::1" - ]; - peers.troisdorf = { - publicKey = "BsQODhcHnEN4tXn60t72iejZvgYvVNt5BDVK+69+RgM="; - endpoint = "fw-tdf.stinnesbeck.com:36432"; - persistentKeepalive = 25; - presharedKeyFile = "${secrets}/wireguard/family.psk"; - allowedIPs = [ - "10.1.0.0/24" - "fd73:f65e:6b64::/48" - ]; - }; - }; - - # Road warrior. - mobile = { - privateKeyFile = "${secrets}/wireguard/mobile-clients.key"; - listenPort = 36433; - zone = "mobile"; - allowManagement = true; - dns = true; - peers.laptop = { - publicKey = "Jts5u8zIs28v6RIEYCCufDDiWXf+gbElT63wUFWQxX4="; - persistentKeepalive = 25; - presharedKeyFile = "${secrets}/wireguard/mobile-clients.psk"; - allowedIPs = [ "10.2.0.2/32" ]; - }; - }; - }; - - # antigua trusted these interfaces completely and forwarded between all of - # them. Here it is spelled out: the LAN and Troisdorf reach each other, the - # LAN reaches the freifunk hosts, road warriors reach the LAN. Nothing - # initiates from freifunk. - firewall.rules = [ - { - from = "lan"; - to = "family"; - } - { - from = "family"; - to = "lan"; - } - { - from = "lan"; - to = "freifunk"; - } - { - from = "mobile"; - to = "lan"; - } - ]; - }; - - # Dynamic DNS from the PPP device's addresses. - services.ddclient = { - enable = true; - interval = "5min"; - protocol = "cloudflare"; - username = "token"; - passwordFile = "${secrets}/cloudflare-ddns-token"; - zone = "stinnesbeck.com"; - domains = [ - "fw-mch.stinnesbeck.com" - "car.k8s.stinnesbeck.com" - "photos.stinnesbeck.com" - "git.stinnesbeck.com" - "link.stinnesbeck.com" - "pw.k8s.stinnesbeck.com" - ]; - usev4 = "ifv4, ifv4=${config.router.wan.ipv4.interface}"; - usev6 = "ifv6, ifv6=${config.router.wan.ipv4.interface}"; - ssl = true; - verbose = true; - }; -} diff --git a/examples/home/import-antigua-wireguard.sh b/examples/home/import-antigua-wireguard.sh deleted file mode 100755 index 4f1ac6c..0000000 --- a/examples/home/import-antigua-wireguard.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -# Takes over antigua's three WireGuard tunnels (freifunk, family, mobile-clients) -# on a router installed from the generic installer. Run as root on the router. -# -# import-antigua-wireguard.sh KEYDIR -# -# KEYDIR holds the secret files copied from antigua's /opt/wireguard: -# freifunk.priv family.priv mobile-clients.priv troisdorf.psk mobile-clients.psk -# e.g. from antigua: -# tar -C /opt/wireguard -czf - freifunk.priv family.priv mobile-clients.priv \ -# troisdorf.psk mobile-clients.psk | ssh root@ROUTER 'mkdir -p /root/wg && tar -xzf - -C /root/wg' -# -# The keys are installed as 0600 files in /var/lib/router/wireguard; the tunnels -# (public settings only), the firewall rules between them and the LAN are -# merged into settings.json. Nothing is applied: look at the WireGuard page, -# then `router-apply` and `router-confirm`. Never run both boxes with the same -# keys at once: stop the tunnels on antigua first. -# -# The tunnels are those of examples/home, translated from antigua's -# wireguard.nix: freifunk accepts 0.0.0.0/0 there but routes just the three -# treasure-maps.com hosts (masqueraded); the road-warrior tunnel is called -# `mobile` (the interface name wg-mobile-clients would be too long). -set -euo pipefail - -state=${STATE_DIR:-/var/lib/router} -settings=${SETTINGS:-$state/config/settings.json} -keys=${1:-} -[ -d "$keys" ] || { echo "usage: $0 KEYDIR" >&2; exit 2; } -[ "$(id -u)" -eq 0 ] || [ -n "${STATE_DIR:-}" ] || { echo "must run as root" >&2; exit 1; } -command -v jq >/dev/null || { echo "jq is needed" >&2; exit 1; } -[ -f "$settings" ] || { echo "no $settings" >&2; exit 1; } - -# antigua's file -> the name it gets here. -declare -A install=( - [freifunk.priv]=freifunk.key - [family.priv]=family.key - [mobile-clients.priv]=mobile.key - [troisdorf.psk]=family-troisdorf.psk - [mobile-clients.psk]=mobile-laptop.psk -) -for f in "${!install[@]}"; do - [ -s "$keys/$f" ] || { echo "missing $keys/$f" >&2; exit 1; } - # A WireGuard key is 32 bytes in base64: 44 characters. - [[ $(tr -d '[:space:]' < "$keys/$f") =~ ^[A-Za-z0-9+/]{43}=$ ]] || { echo "$f is not a WireGuard key" >&2; exit 1; } -done -if jq -e '.router.wireguard // {} | has("freifunk") or has("family") or has("mobile")' "$settings" >/dev/null; then - echo "settings.json has a freifunk, family or mobile tunnel already; not touching it" >&2 - exit 1 -fi -lan=$(jq -r '[.router.lans[]?.ipv4.address][0] // empty' "$settings") -lan_zone=$(jq -r '[.router.lans // {} | to_entries[] | (.value.zone // .key)][0] // empty' "$settings") -[ -n "$lan" ] && [ -n "$lan_zone" ] || { echo "settings.json has no LAN" >&2; exit 1; } - -dest=$state/wireguard -install -d -m 0700 "$dest" -for f in "${!install[@]}"; do - (umask 077; tr -d '[:space:]' < "$keys/$f" > "$dest/${install[$f]}"; echo >> "$dest/${install[$f]}") -done - -cp -a "$settings" "$settings.before-wireguard-import" -jq --arg dest "$dest" --arg lan "$lan" --arg lanzone "$lan_zone" ' - .router.wireguard = ((.router.wireguard // {}) + { - freifunk: { - privateKeyFile: "\($dest)/freifunk.key", - masquerade: true, - addresses: ["10.255.1.8/32", "fd80:3ea2:e399:203a::8/128"], - peers: { supernode: { - publicKey: "5B/YTaDPVWVApUyHshJp899iXXlBy8rBqJUpYvKo+1s=", - endpoint: "5.9.220.114:42001", - persistentKeepalive: 15, - allowedIPs: ["185.90.196.65/32", "185.90.196.97/32", "185.90.196.129/32"] - } } - }, - family: { - privateKeyFile: "\($dest)/family.key", - listenPort: 36432, - allowManagement: true, - dns: true, - sourceAddresses: [$lan], - peers: { troisdorf: { - publicKey: "BsQODhcHnEN4tXn60t72iejZvgYvVNt5BDVK+69+RgM=", - endpoint: "fw-tdf.stinnesbeck.com:36432", - persistentKeepalive: 25, - presharedKeyFile: "\($dest)/family-troisdorf.psk", - allowedIPs: ["10.1.0.0/24", "fd73:f65e:6b64::/48"] - } } - }, - mobile: { - privateKeyFile: "\($dest)/mobile.key", - listenPort: 36433, - allowManagement: true, - dns: true, - peers: { laptop: { - publicKey: "Jts5u8zIs28v6RIEYCCufDDiWXf+gbElT63wUFWQxX4=", - persistentKeepalive: 25, - presharedKeyFile: "\($dest)/mobile-laptop.psk", - allowedIPs: ["10.2.0.2/32"] - } } - } - }) - # antigua trusted these interfaces completely; spelled out: the LAN and - # Troisdorf reach each other, the LAN reaches the freifunk hosts, road - # warriors reach the LAN. Nothing initiates from freifunk. - | .router.firewall.rules = ((.router.firewall.rules // []) + [ - { from: $lanzone, to: "family" }, - { from: "family", to: $lanzone }, - { from: $lanzone, to: "freifunk" }, - { from: "mobile", to: $lanzone } - ])' "$settings.before-wireguard-import" > "$settings.new" -chmod 600 "$settings.new" -mv "$settings.new" "$settings" - -echo "Imported freifunk, family and mobile (keys in $dest)." -echo "Look at the WireGuard page, then: router-apply, and router-confirm when the LAN still works." -echo "The previous settings are in $settings.before-wireguard-import. Remove $keys when done." diff --git a/flake.nix b/flake.nix index ee5871f..28c164e 100644 --- a/flake.nix +++ b/flake.nix @@ -272,10 +272,6 @@ ]; }; - packages.x86_64-linux.installer-home = - (self.lib.mkInstaller { configuration = self.nixosConfigurations.home; }) - .config.system.build.isoImage; - packages.x86_64-linux.installer-example = (self.lib.mkInstaller { configuration = self.nixosConfigurations.example; }) .config.system.build.isoImage; @@ -289,15 +285,6 @@ ]; }; - # The home gateway replacing antigua's router role (see examples/home). - nixosConfigurations.home = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ - self.nixosModules.router - ./examples/home - ]; - }; - # Appliance image (verity store, volatile root) for trying on hardware: # nix build .#image -> result/*.raw nixosConfigurations.image = nixpkgs.lib.nixosSystem { @@ -365,7 +352,6 @@ // { # The example must keep building as a complete system. example = self.nixosConfigurations.example.config.system.build.toplevel; - home = self.nixosConfigurations.home.config.system.build.toplevel; }; # `nix run .#lab`: boot the basic test topology in tmux, one console pane diff --git a/scripts/fast.sh b/scripts/fast.sh index 8024171..0f6c66a 100644 --- a/scripts/fast.sh +++ b/scripts/fast.sh @@ -1,8 +1,8 @@ # The quick checks: no VM boots. Catches what most failed runs of the VM tests # were about (Go and Nix errors, shellcheck, type errors in the test scripts, # assertions). Evaluating NixOS is the cost: about a minute for the default. -# router-fast Go checks; the installer, a settings-built router -# (via the release) and the home gateway build +# router-fast Go checks; the installer and a settings-built router +# (via the release) build # router-fast --drivers also build every VM test's driver (type-checks the # test scripts, builds the VMs' systems): minutes # router-fast --all also every flake check without VMs (assertions, @@ -40,7 +40,6 @@ fi step "systems build (shellcheck of the scripts, units, firewall rules)" nix build --no-link --max-jobs 1 \ .#nixosConfigurations.installer.config.system.build.toplevel \ - .#nixosConfigurations.home.config.system.build.toplevel \ .#release if [ "$drivers" = 1 ]; then @@ -52,7 +51,7 @@ if [ "$drivers" = 1 ]; then fi if [ "$all" = 1 ]; then step "the checks that need no VM" - nix build --no-link --max-jobs 1 .#checks.x86_64-linux.{lib,assertions,example,home} + nix build --no-link --max-jobs 1 .#checks.x86_64-linux.{lib,assertions,example} step "everything evaluates" nix flake check --no-build fi