heather: replace personal mesh with guarded AstrNet stub
This commit is contained in:
parent
4efbdc9bda
commit
2ad5653fda
4 changed files with 203 additions and 175 deletions
199
hosts/heather/astrnet.nix
Normal file
199
hosts/heather/astrnet.nix
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
# astrnet.nix — AstrNet stub peer "michaotic". Generated by astrnet-join.sh.
|
||||
#
|
||||
# What this host is to the mesh: identity 10.0.0.9 / 2a05:f480:2400:1cf7::9,
|
||||
# ASN 4200000009, two WireGuard legs that THIS host dials:
|
||||
# wg-mic-der -> alderamin (Helsinki) primary, pair 38
|
||||
# wg-mic-bd8 -> bd8816 (Moscow) backup, pair 39 (paths come prepended)
|
||||
# It announces only its identity and accepts only mesh address space; it
|
||||
# never becomes transit and never takes a default route from the mesh.
|
||||
#
|
||||
# All values are assigned by the AstrNet admin. Do not renumber anything
|
||||
# here without them: the far ends are configured to match exactly.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
keyFile = "/var/lib/astrnet/wg.key";
|
||||
id4 = "10.0.0.9";
|
||||
id6 = "2a05:f480:2400:1cf7::9";
|
||||
|
||||
mkLeg = { listenPort, ips, publicKey, endpoint }: {
|
||||
inherit ips listenPort;
|
||||
privateKeyFile = keyFile;
|
||||
mtu = 1380;
|
||||
# Routing is BGP's job. allowedIPs 0/0 only PERMITS any source through
|
||||
# the tunnel; with routes off, it installs nothing. Turning routes on
|
||||
# would hijack this host's default route.
|
||||
allowedIPsAsRoutes = false;
|
||||
peers = [{
|
||||
inherit publicKey endpoint;
|
||||
allowedIPs = [ "0.0.0.0/0" "::/0" ];
|
||||
persistentKeepalive = 25;
|
||||
}];
|
||||
};
|
||||
in {
|
||||
networking.wireguard.interfaces = {
|
||||
# The fe80:: link-locals are required: without one FRR's IPv6 BGP
|
||||
# next-hop selection fails and the v6 session never leaves Connect.
|
||||
wg-mic-der = mkLeg {
|
||||
listenPort = 52038;
|
||||
ips = [ "10.99.0.154/30" "fd53:4023:2326:99::9a/126" "fe80::2/64" ];
|
||||
publicKey = "a4FyXuoI16TxZxv407lSzJV2YFJdq6FgRAtGu+oMzEY=";
|
||||
endpoint = "31.77.105.157:52038";
|
||||
};
|
||||
wg-mic-bd8 = mkLeg {
|
||||
listenPort = 52039;
|
||||
ips = [ "10.99.0.158/30" "fd53:4023:2326:99::9e/126" "fe80::2/64" ];
|
||||
publicKey = "QIvX+avvJqaHxuksGWb6Hx8fv7R6VI5wpaGqLme6Fgw=";
|
||||
endpoint = "195.133.60.217:52039";
|
||||
};
|
||||
};
|
||||
|
||||
# Heather's public firewall is disabled (the cloud SG is the WAN barrier).
|
||||
# Explicitly restrict traffic FROM AstrNet before normal Docker/host rules:
|
||||
# BGP from the two expected neighbors, ping to this mesh ID; no transit.
|
||||
# Existing outbound sessions may receive established replies.
|
||||
systemd.services.astrnet-guard = {
|
||||
description = "Restrict AstrNet tunnel ingress";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
requiredBy = [ "wireguard-wg-mic-der.service" "wireguard-wg-mic-bd8.service" ];
|
||||
before = [ "wireguard-wg-mic-der.service" "wireguard-wg-mic-bd8.service" ];
|
||||
path = [ pkgs.nftables ];
|
||||
serviceConfig = { Type = "oneshot"; RemainAfterExit = true; };
|
||||
script = ''
|
||||
nft list table inet astrnet_guard >/dev/null 2>&1 && nft delete table inet astrnet_guard
|
||||
nft -f - <<'RULES'
|
||||
table inet astrnet_guard {
|
||||
chain input {
|
||||
type filter hook input priority -150; policy accept;
|
||||
iifname { "wg-mic-der", "wg-mic-bd8" } ct state established,related accept
|
||||
iifname "wg-mic-der" ip saddr 10.99.0.153 tcp dport 179 accept
|
||||
iifname "wg-mic-bd8" ip saddr 10.99.0.157 tcp dport 179 accept
|
||||
iifname "wg-mic-der" ip6 saddr fd53:4023:2326:99::99 tcp dport 179 accept
|
||||
iifname "wg-mic-bd8" ip6 saddr fd53:4023:2326:99::9d tcp dport 179 accept
|
||||
iifname { "wg-mic-der", "wg-mic-bd8" } ip daddr 10.0.0.9 icmp type echo-request accept
|
||||
iifname { "wg-mic-der", "wg-mic-bd8" } drop
|
||||
}
|
||||
chain forward {
|
||||
type filter hook forward priority -150; policy accept;
|
||||
iifname { "wg-mic-der", "wg-mic-bd8" } drop
|
||||
}
|
||||
}
|
||||
RULES
|
||||
'';
|
||||
preStop = "nft delete table inet astrnet_guard || true";
|
||||
};
|
||||
|
||||
# Mesh identity on a dummy interface. It must exist before FRR installs
|
||||
# routes, because those routes are pinned to it as their source address.
|
||||
systemd.services.astrnet-mesh0 = {
|
||||
description = "AstrNet mesh identity (mesh0)";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "frr.service" ];
|
||||
requiredBy = [ "frr.service" ];
|
||||
path = [ pkgs.iproute2 ];
|
||||
serviceConfig = { Type = "oneshot"; RemainAfterExit = true; };
|
||||
script = ''
|
||||
ip link show mesh0 >/dev/null 2>&1 || ip link add mesh0 type dummy
|
||||
ip link set mesh0 up
|
||||
ip addr replace ${id4}/32 dev mesh0
|
||||
ip -6 addr replace ${id6}/128 dev mesh0
|
||||
'';
|
||||
preStop = "ip link del mesh0 || true";
|
||||
};
|
||||
|
||||
# BGP only inside the tunnels, never on the public interface.
|
||||
networking.firewall.interfaces."wg-mic-der".allowedTCPPorts = [ 179 ];
|
||||
networking.firewall.interfaces."wg-mic-bd8".allowedTCPPorts = [ 179 ];
|
||||
# Two legs means replies can arrive on the leg that is not the best route
|
||||
# back. Strict reverse-path filtering drops those; loose keeps the spoof
|
||||
# check (a route must exist) without caring which leg it points at.
|
||||
networking.firewall.checkReversePath = lib.mkDefault "loose";
|
||||
|
||||
networking.networkmanager.unmanaged =
|
||||
lib.mkIf config.networking.networkmanager.enable
|
||||
[ "interface-name:wg-mic-*" "interface-name:mesh0" ];
|
||||
|
||||
services.frr = {
|
||||
bgpd.enable = true;
|
||||
config = ''
|
||||
ip prefix-list OWN-V4 seq 5 permit ${id4}/32
|
||||
ipv6 prefix-list OWN-V6 seq 5 permit ${id6}/128
|
||||
!
|
||||
! Accept mesh space only: never a default, never public prefixes. The
|
||||
! mesh carries anycast /32s for public services (DNS resolvers etc.);
|
||||
! accepting those would steer this host's own traffic into AstrNet.
|
||||
! It also keeps the tunnel endpoints themselves out of the tunnels.
|
||||
! Narrow to AstrNet address space; do not accept Yandex Cloud 10.130/24.
|
||||
ip prefix-list MESH-V4 seq 5 permit 10.0.0.0/24 ge 24 le 32
|
||||
ip prefix-list MESH-V4 seq 10 permit 10.0.1.0/24 ge 24 le 32
|
||||
ip prefix-list MESH-V4 seq 15 permit 10.0.2.0/24 ge 24 le 32
|
||||
ip prefix-list MESH-V4 seq 20 permit 10.99.0.0/22 ge 22 le 32
|
||||
ipv6 prefix-list MESH-V6 seq 5 permit 2a05:f480:2400:1cf7::/64 le 128
|
||||
ipv6 prefix-list MESH-V6 seq 10 permit fd53:4023:2326::/48 le 128
|
||||
!
|
||||
route-map MESH-IN-V4 permit 10
|
||||
match ip address prefix-list MESH-V4
|
||||
exit
|
||||
route-map MESH-IN-V6 permit 10
|
||||
match ipv6 address prefix-list MESH-V6
|
||||
exit
|
||||
route-map MESH-OUT-V4 permit 10
|
||||
match ip address prefix-list OWN-V4
|
||||
exit
|
||||
route-map MESH-OUT-V6 permit 10
|
||||
match ipv6 address prefix-list OWN-V6
|
||||
exit
|
||||
!
|
||||
! Source mesh traffic from the identity, not from whichever tunnel /30
|
||||
! it leaves by; otherwise replies break whenever the path changes.
|
||||
route-map SET-MESH-SRC permit 10
|
||||
set src ${id4}
|
||||
exit
|
||||
route-map SET-MESH-SRC-V6 permit 10
|
||||
set src ${id6}
|
||||
exit
|
||||
ip protocol bgp route-map SET-MESH-SRC
|
||||
ipv6 protocol bgp route-map SET-MESH-SRC-V6
|
||||
!
|
||||
router bgp 4200000009
|
||||
bgp router-id ${id4}
|
||||
no bgp ebgp-requires-policy
|
||||
no bgp default ipv4-unicast
|
||||
bgp bestpath as-path multipath-relax
|
||||
neighbor 10.99.0.153 remote-as 4200000012
|
||||
neighbor 10.99.0.153 description alderamin-v4-wg-mic-der
|
||||
neighbor 10.99.0.153 update-source 10.99.0.154
|
||||
neighbor 10.99.0.157 remote-as 4200000005
|
||||
neighbor 10.99.0.157 description bd8816-v4-wg-mic-bd8
|
||||
neighbor 10.99.0.157 update-source 10.99.0.158
|
||||
neighbor fd53:4023:2326:99::99 remote-as 4200000012
|
||||
neighbor fd53:4023:2326:99::99 description alderamin-v6-wg-mic-der
|
||||
neighbor fd53:4023:2326:99::99 interface wg-mic-der
|
||||
neighbor fd53:4023:2326:99::99 update-source fd53:4023:2326:99::9a
|
||||
neighbor fd53:4023:2326:99::9d remote-as 4200000005
|
||||
neighbor fd53:4023:2326:99::9d description bd8816-v6-wg-mic-bd8
|
||||
neighbor fd53:4023:2326:99::9d interface wg-mic-bd8
|
||||
neighbor fd53:4023:2326:99::9d update-source fd53:4023:2326:99::9e
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network ${id4}/32
|
||||
neighbor 10.99.0.153 activate
|
||||
neighbor 10.99.0.153 route-map MESH-IN-V4 in
|
||||
neighbor 10.99.0.153 route-map MESH-OUT-V4 out
|
||||
neighbor 10.99.0.157 activate
|
||||
neighbor 10.99.0.157 route-map MESH-IN-V4 in
|
||||
neighbor 10.99.0.157 route-map MESH-OUT-V4 out
|
||||
exit-address-family
|
||||
!
|
||||
address-family ipv6 unicast
|
||||
network ${id6}/128
|
||||
neighbor fd53:4023:2326:99::99 activate
|
||||
neighbor fd53:4023:2326:99::99 route-map MESH-IN-V6 in
|
||||
neighbor fd53:4023:2326:99::99 route-map MESH-OUT-V6 out
|
||||
neighbor fd53:4023:2326:99::9d activate
|
||||
neighbor fd53:4023:2326:99::9d route-map MESH-IN-V6 in
|
||||
neighbor fd53:4023:2326:99::9d route-map MESH-OUT-V6 out
|
||||
exit-address-family
|
||||
exit
|
||||
'';
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue