From 1a6f9ffb8f33ae05148cdc03aa0df4f22dbde28d Mon Sep 17 00:00:00 2001 From: Raito Bezarius Date: Fri, 12 Jan 2024 02:22:05 +0100 Subject: [PATCH] router03: perfect refactor into a proper NixOS module We will focus on growing it for KlubRZ usecases first and then grow it into a proper external project called Hypervisor NixOS routers. --- machines/router03/router.nix | 359 +++-------------------------------- modules/default.nix | 1 + modules/krz-router.nix | 323 +++++++++++++++++++++++++++++++ 3 files changed, 351 insertions(+), 332 deletions(-) create mode 100644 modules/krz-router.nix diff --git a/machines/router03/router.nix b/machines/router03/router.nix index 248ba72..85a2ac4 100644 --- a/machines/router03/router.nix +++ b/machines/router03/router.nix @@ -1,341 +1,36 @@ { config, pkgs, lib, ... }: -let - inherit (lib) mkOption types; - mkVLAN = name: id: { - netdevConfig = { - Kind = "vlan"; - Name = name; - }; - vlanConfig.Id = id; - }; - mkTunnel = kind: name: { local, remote, mtu ? 1480 }: { - netdevConfig = { - Kind = kind; - Name = name; - MTUBytes = toString mtu; - }; - tunnelConfig = { - Local = local; - Remote = remote; - }; - }; - vip = config.router.wan.vip; - rip = config.router.wan.rip; -in { - options.router = { - wan = { - vip = mkOption { - type = types.str; - description = "Highly-available virtual IP address of the router"; - }; - rip = mkOption { - type = types.str; - description = "Real IP address of the router"; - }; - }; + krz-router = { + enable = true; + enablePrimary = true; + vip = "129.199.146.230"; + rip = "129.199.146.231"; + trunkPort.macAddress = "92:E3:9C:CE:EF:14"; }; - config = { - router.wan.vip = "129.199.146.230"; - router.wan.rip = "129.199.146.231"; - networking.firewall.allowedUDPPorts = [ 25351 ]; - systemd.network.enable = true; - networking.dhcpcd.enable = false; - systemd.network.links."10-swp" = { - matchConfig.MACAddress = "92:E3:9C:CE:EF:14"; - linkConfig.Name = "swp"; - }; - systemd.network = { - config.routeTables = { - he = 100; - mwan = 110; - }; - netdevs = { - "05-admin-vpn" = { - netdevConfig = { - Kind = "wireguard"; - Name = "wgadmin"; - MTUBytes = "1420"; - }; - wireguardConfig = { - PrivateKeyFile = "/etc/secrets/wireguard/wgadmin"; - ListenPort = 25351; - }; - wireguardPeers = [ - { - wireguardPeerConfig = { - PublicKey = "obsUPq4Y1XGbl3yPUytPKkVcSP+eECpaQX+bV+ocwXg="; - AllowedIPs = [ "fd81:fb3a:50cc::100/128" ]; - }; - } - ]; - }; - "10-tun-mwan" = mkTunnel "gre" "gre-mwan" { - remote = "80.67.167.30"; - local = vip; - }; - "10-tun-he" = mkTunnel "sit" "sit-he" { - remote = "216.66.84.42"; - local = vip; - }; - # VLANs - # 401: uplink ENS - # 3500: intranet club réseau, proxy ARP et proxy arp pvlan / 10.1.1.1/22 - # 3510: mgmt club réseau (administration network) / fd81:fb3a:50cc::/64 - # 3605: MWAN V6 DMZ / 2a0e:e701:1120:b00c::1/64 - # 3606: MWAN V4 DMZ / 45.13.104.25/29 - # 3607: Club Réseau v6 DMZ (en ASN propre) - # 3608: DN42 DMZ - # 3609: HE V6 DMZ / 2001:470:1f13:187::1/64 - # 3610: Free V6 DMZ - # 3620: HE.net IPv6 /48 -> DHCP-PD /60 - # 3621: MWAN DMZ /48 PD delivery / 2a0e:e701:1120::1/48 - # 3622: Router VRRP link / $to_be_determined. - # "10-uplink-ens" = mkVLAN "uplink-ens" 401; dysfunctional? - "10-intranet-krz" = mkVLAN "intranet-krz" 3500; - "10-admin" = mkVLAN "admin" 3510; - "10-mwan-v6" = mkVLAN "mwan-v6" 3605; - "10-mwan-dual" = mkVLAN "mwan-dual" 3606; - "10-krz-v6" = mkVLAN "krz-v6" 3607; - "10-dn42-dmz" = mkVLAN "dn42-dmz" 3608; - "10-he-dmz" = mkVLAN "he-dmz" 3609; - "10-free-dmz" = mkVLAN "free-dmz" 3610; - "10-he-pd" = mkVLAN "he-v6-pd" 3620; - "10-mwan-pd" = mkVLAN "mwan-v6-pd" 3621; - "10-vrrp-router" = mkVLAN "vrrp-router" 3622; - }; - networks = { - "10-admin-vpn" = { - matchConfig.Name = "wgadmin"; - networkConfig = { - Description = "VPN d'administration système de l'infrastructure"; - Address = [ "fd81:fb3a:50cc::1/64" ]; - # Give access to the rest of the network. - IPForward = "ipv6"; - ConfigureWithoutCarrier = true; - }; - linkConfig.RequiredForOnline = "routable"; - }; - "15-admin-vlan" = { - matchConfig.Name = "admin"; - networkConfig = { - Description = "VLAN d'administration système de l'infrastructure"; - Address = [ "fd81:fb3a:50cc:1::1/48" ]; - # Give access to the rest of the network. - IPForward = "ipv6"; - IPv6ProxyNDP = true; - ConfigureWithoutCarrier = true; - }; - linkConfig.RequiredForOnline = "routable"; - }; - "20-tun-mwan" = { - matchConfig.Name = "gre-mwan"; - networkConfig = { - Description = "Tunnel de livraison GRE IPv4/IPv6 de MilkyWAN"; - Address = [ "10.1.1.50/30" "2a0b:cbc0:1::216/126" ]; - ConfigureWithoutCarrier = true; - }; - routes = [ - { - routeConfig = { - Gateway = "10.1.1.49"; - Table = "mwan"; - Scope = "global"; - # FIXME(raito): Has no effect? Upstream bug? - Source = "45.13.104.25/29"; - }; - } - { - routeConfig = { - Destination = "::/0"; - Gateway = "2a0b:cbc0:1::215"; - Table = "mwan"; - Scope = "global"; - Source = "2a0e:e701:1120::/48"; - }; - } - ]; - routingPolicyRules = [ - { - routingPolicyRuleConfig = { - From = "2a0e:e701:1120::/48"; - Table = "mwan"; - }; - } - { - routingPolicyRuleConfig = { - From = "45.13.104.25/29"; - Table = "mwan"; - }; - } - { - routingPolicyRuleConfig = { - To = "45.13.104.25/29"; - Table = "mwan"; - }; - } - ]; - }; - "20-tun-he" = { - matchConfig.Name = "sit-he"; - networkConfig = { - Description = "HE.NET IPv6 Tunnel (owned by gdd)"; - Address = [ "2001:470:1f12:187::2/64" ]; - ConfigureWithoutCarrier = true; - }; - routes = [ - { - routeConfig = { - Destination = "::/0"; - Table = "he"; - Scope = "global"; - Source = "2001:470:1f13::/48"; - }; - } - ]; - routingPolicyRules = [ - { - routingPolicyRuleConfig = { - From = "2001:470:1f13::/48"; - Table = "he"; - }; - } - ]; - }; - "10-swp" = { - matchConfig.Name = "swp"; - networkConfig = { - Description = "VLAN-aware switch port"; - Address = [ "${rip}/24" ]; - Gateway = "129.199.146.254"; - LLDP = true; - # Only to the switch we are connected to directly, e.g. the hypervisor or the switch. - EmitLLDP = "nearest-bridge"; - # For VRRP. - KeepConfiguration = true; - }; - routingPolicyRules = [ - { - routingPolicyRuleConfig = { - From = "45.13.104.25/29"; - Type = "prohibit"; - }; - } - ]; - tunnel = [ - "gre-mwan" - "sit-he" - ]; - vlan = [ - # "intranet-krz" - we don't want to keep this. - "admin" - # FIXME: "mwan-v6" - do we want to keep this? - # We can achieve v6-only by enforcing MAC address isolation for IPv4. - "mwan-dual" - # FIXME: legacy-nat-zone. - # FIXME: "krz-v6" - not ready yet. - # FIXME: "dn42-dmz" - revive this if you want. - "he-dmz" - # FIXME: "free-dmz" - not ready yet, abandoned? - # FIXME: "he-v6-pd" - require rework - # FIXME: "mwan-v6-pd" - require rework - ]; - }; - # TODO: SIIT/NAT64/DNS64 component to avoid IPv4 dependency. - "20-mwan-dual" = { - matchConfig.Name = "mwan-dual"; - addresses = [ - { - addressConfig = { - Address = "2a0e:e701:1120:b00c::1/64"; - AddPrefixRoute = false; - }; - } - { - addressConfig = { - Address = "45.13.104.25/29"; - AddPrefixRoute = false; - }; - } - ]; - routes = [ - { - routeConfig = { - Destination = "2a0e:e701:1120:b00c::/64"; - Metric = 256; - Table = "mwan"; - }; - } - { - routeConfig = { - Destination = "45.13.104.25/29"; - Metric = 256; - Table = "mwan"; - }; - } - ]; - networkConfig = { - Description = "MilkyWAN dual stack public interface"; - DHCPServer = true; - IPv6SendRA = true; - IPForward = true; - ConfigureWithoutCarrier = true; - }; - }; - "20-he-dmz" = { - matchConfig.Name = "he-dmz"; - addresses = [ - { - addressConfig = { - Address = "2001:470:1f13:187::1/64"; - # This will add it in the wrong table. - # TODO: add to systemd a `Table` option here. - AddPrefixRoute = false; - }; - } - ]; - routes = [ - { - routeConfig = { - Destination = "2001:470:1f13:187::/64"; - Metric = 256; - Table = "he"; - }; - } - ]; - networkConfig = { - Description = "Hurricane Electrical's 187 /64 unfirewalled zone"; - IPv6SendRA = true; - ConfigureWithoutCarrier = true; - }; - }; - }; - }; - # services.keepalived.enable = true; - # services.keepalived.vrrpInstances.wan = { - # interface = "swp"; - # state = "MASTER"; - # priority = 50; - # virtualIps = [{ addr = "129.199.146.230"; }]; - # virtualRouterId = 1; - # }; +# services.keepalived.enable = true; +# services.keepalived.vrrpInstances.wan = { +# interface = "swp"; +# state = "MASTER"; +# priority = 50; +# virtualIps = [{ addr = "129.199.146.230"; }]; +# virtualRouterId = 1; +# }; - # systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug"; - environment.systemPackages = [ pkgs.tcpdump pkgs.wireguard-tools ]; + # systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug"; + environment.systemPackages = [ pkgs.tcpdump pkgs.wireguard-tools ]; - # Zone based firewall + # Zone based firewall - # Flow accounting in PostgreSQL. - services.postgresql = { - enable = true; - ensureUsers = []; - }; - # services.ulogd = { - # enable = true; - # settings = { - # }; - # }; - }; + # Flow accounting in PostgreSQL. + services.postgresql = { + enable = true; + ensureUsers = []; + }; +# services.ulogd = { +# enable = true; +# settings = { +# }; +# }; } diff --git a/modules/default.nix b/modules/default.nix index 4d862f7..5d75a20 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -4,6 +4,7 @@ imports = (lib.extra.mkImports ./. [ "krz-access-control" "krz-ssh" + "krz-router" ]) ++ [ # TODO: Switch to global version of agenix via npins # "${sources.agenix}/modules/age.nix" diff --git a/modules/krz-router.nix b/modules/krz-router.nix new file mode 100644 index 0000000..e1c4c91 --- /dev/null +++ b/modules/krz-router.nix @@ -0,0 +1,323 @@ +{ config, lib, ... }: +let + inherit (lib) + mkIf mkEnableOption mkOption types; + cfg = config.krz-router; + mkVLAN = name: id: { + netdevConfig = { + Kind = "vlan"; + Name = name; + }; + vlanConfig.Id = id; + }; + mkTunnel = kind: name: { local, remote, mtu ? 1480 }: { + netdevConfig = { + Kind = kind; + Name = name; + MTUBytes = toString mtu; + }; + tunnelConfig = { + Local = local; + Remote = remote; + }; + }; +in +{ + options.krz-router = { + enable = mkEnableOption "KlubRZ router"; + enablePrimary = mkEnableOption ''primary mode for this router. + This means that this router will assume the primary role by default. + Do not run on the same L2 segment the same router as primary. + ''; + enableDebug = mkEnableOption "debug mode for the various subsystems"; + trunkPort.macAddress = mkOption { + type = types.str; + description = "MAC address of the trunk port connected to a (virtual) switch"; + }; + vip = mkOption { + type = types.str; + description = "Highly-available virtual IP address of the router"; + }; + rip = mkOption { + type = types.str; + description = "Real IP address of the router"; + }; + }; + + config = mkIf cfg.enable { + systemd.network.links."10-swp" = { + matchConfig.MACAddress = cfg.trunkPort.macAddress; + linkConfig.Name = "swp"; + }; + + networking.firewall.allowedUDPPorts = [ 25351 ]; + systemd.network.enable = true; + networking.dhcpcd.enable = false; + systemd.network = { + config.routeTables = { + he = 100; + mwan = 110; + }; + netdevs = { + "05-admin-vpn" = { + netdevConfig = { + Kind = "wireguard"; + Name = "wgadmin"; + MTUBytes = "1420"; + }; + wireguardConfig = { + PrivateKeyFile = "/etc/secrets/wireguard/wgadmin"; + ListenPort = 25351; + }; + wireguardPeers = [ + { + wireguardPeerConfig = { + PublicKey = "obsUPq4Y1XGbl3yPUytPKkVcSP+eECpaQX+bV+ocwXg="; + AllowedIPs = [ "fd81:fb3a:50cc::100/128" ]; + }; + } + ]; + }; + "10-tun-mwan" = mkTunnel "gre" "gre-mwan" { + remote = "80.67.167.30"; + local = cfg.vip; + }; + "10-tun-he" = mkTunnel "sit" "sit-he" { + remote = "216.66.84.42"; + local = cfg.vip; + }; + # VLANs + # 401: uplink ENS + # 3500: intranet club réseau, proxy ARP et proxy arp pvlan / 10.1.1.1/22 + # 3510: mgmt club réseau (administration network) / fd81:fb3a:50cc::/64 + # 3605: MWAN V6 DMZ / 2a0e:e701:1120:b00c::1/64 + # 3606: MWAN V4 DMZ / 45.13.104.25/29 + # 3607: Club Réseau v6 DMZ (en ASN propre) + # 3608: DN42 DMZ + # 3609: HE V6 DMZ / 2001:470:1f13:187::1/64 + # 3610: Free V6 DMZ + # 3620: HE.net IPv6 /48 -> DHCP-PD /60 + # 3621: MWAN DMZ /48 PD delivery / 2a0e:e701:1120::1/48 + # 3622: Router VRRP link / $to_be_determined. + # "10-uplink-ens" = mkVLAN "uplink-ens" 401; dysfunctional? + "10-intranet-krz" = mkVLAN "intranet-krz" 3500; + "10-admin" = mkVLAN "admin" 3510; + "10-mwan-v6" = mkVLAN "mwan-v6" 3605; + "10-mwan-dual" = mkVLAN "mwan-dual" 3606; + "10-krz-v6" = mkVLAN "krz-v6" 3607; + "10-dn42-dmz" = mkVLAN "dn42-dmz" 3608; + "10-he-dmz" = mkVLAN "he-dmz" 3609; + "10-free-dmz" = mkVLAN "free-dmz" 3610; + "10-he-pd" = mkVLAN "he-v6-pd" 3620; + "10-mwan-pd" = mkVLAN "mwan-v6-pd" 3621; + "10-vrrp-router" = mkVLAN "vrrp-router" 3622; + }; + networks = { + "10-admin-vpn" = { + matchConfig.Name = "wgadmin"; + networkConfig = { + Description = "VPN d'administration système de l'infrastructure"; + Address = [ "fd81:fb3a:50cc::1/64" ]; + # Give access to the rest of the network. + IPForward = "ipv6"; + ConfigureWithoutCarrier = true; + }; + linkConfig.RequiredForOnline = "routable"; + }; + "15-admin-vlan" = { + matchConfig.Name = "admin"; + networkConfig = { + Description = "VLAN d'administration système de l'infrastructure"; + Address = [ "fd81:fb3a:50cc:1::1/48" ]; + # Give access to the rest of the network. + IPForward = "ipv6"; + IPv6ProxyNDP = true; + ConfigureWithoutCarrier = true; + }; + linkConfig.RequiredForOnline = "routable"; + }; + "20-tun-mwan" = { + matchConfig.Name = "gre-mwan"; + networkConfig = { + Description = "Tunnel de livraison GRE IPv4/IPv6 de MilkyWAN"; + Address = [ "10.1.1.50/30" "2a0b:cbc0:1::216/126" ]; + ConfigureWithoutCarrier = true; + }; + routes = [ + { + routeConfig = { + Gateway = "10.1.1.49"; + Table = "mwan"; + Scope = "global"; + # FIXME(raito): Has no effect? Upstream bug? + Source = "45.13.104.25/29"; + }; + } + { + routeConfig = { + Destination = "::/0"; + Gateway = "2a0b:cbc0:1::215"; + Table = "mwan"; + Scope = "global"; + Source = "2a0e:e701:1120::/48"; + }; + } + ]; + routingPolicyRules = [ + { + routingPolicyRuleConfig = { + From = "2a0e:e701:1120::/48"; + Table = "mwan"; + }; + } + { + routingPolicyRuleConfig = { + From = "45.13.104.25/29"; + Table = "mwan"; + }; + } + { + routingPolicyRuleConfig = { + To = "45.13.104.25/29"; + Table = "mwan"; + }; + } + ]; + }; + "20-tun-he" = { + matchConfig.Name = "sit-he"; + networkConfig = { + Description = "HE.NET IPv6 Tunnel (owned by gdd)"; + Address = [ "2001:470:1f12:187::2/64" ]; + ConfigureWithoutCarrier = true; + }; + routes = [ + { + routeConfig = { + Destination = "::/0"; + Table = "he"; + Scope = "global"; + Source = "2001:470:1f13::/48"; + }; + } + ]; + routingPolicyRules = [ + { + routingPolicyRuleConfig = { + From = "2001:470:1f13::/48"; + Table = "he"; + }; + } + ]; + }; + "10-swp" = { + matchConfig.Name = "swp"; + networkConfig = { + Description = "VLAN-aware switch port"; + Address = [ "${cfg.rip}/24" ]; + Gateway = "129.199.146.254"; + LLDP = true; + # Only to the switch we are connected to directly, e.g. the hypervisor or the switch. + EmitLLDP = "nearest-bridge"; + # For VRRP. + KeepConfiguration = true; + }; + routingPolicyRules = [ + { + routingPolicyRuleConfig = { + From = "45.13.104.25/29"; + Type = "prohibit"; + }; + } + ]; + tunnel = [ + "gre-mwan" + "sit-he" + ]; + vlan = [ + # "intranet-krz" - we don't want to keep this. + "admin" + # FIXME: "mwan-v6" - do we want to keep this? + # We can achieve v6-only by enforcing MAC address isolation for IPv4. + "mwan-dual" + # FIXME: legacy-nat-zone. + # FIXME: "krz-v6" - not ready yet. + # FIXME: "dn42-dmz" - revive this if you want. + "he-dmz" + # FIXME: "free-dmz" - not ready yet, abandoned? + # FIXME: "he-v6-pd" - require rework + # FIXME: "mwan-v6-pd" - require rework + ]; + }; + # TODO: SIIT/NAT64/DNS64 component to avoid IPv4 dependency. + "20-mwan-dual" = { + matchConfig.Name = "mwan-dual"; + addresses = [ + { + addressConfig = { + Address = "2a0e:e701:1120:b00c::1/64"; + AddPrefixRoute = false; + }; + } + { + addressConfig = { + Address = "45.13.104.25/29"; + AddPrefixRoute = false; + }; + } + ]; + routes = [ + { + routeConfig = { + Destination = "2a0e:e701:1120:b00c::/64"; + Metric = 256; + Table = "mwan"; + }; + } + { + routeConfig = { + Destination = "45.13.104.25/29"; + Metric = 256; + Table = "mwan"; + }; + } + ]; + networkConfig = { + Description = "MilkyWAN dual stack public interface"; + DHCPServer = true; + IPv6SendRA = true; + IPForward = true; + ConfigureWithoutCarrier = true; + }; + }; + "20-he-dmz" = { + matchConfig.Name = "he-dmz"; + addresses = [ + { + addressConfig = { + Address = "2001:470:1f13:187::1/64"; + # This will add it in the wrong table. + # TODO: add to systemd a `Table` option here. + AddPrefixRoute = false; + }; + } + ]; + routes = [ + { + routeConfig = { + Destination = "2001:470:1f13:187::/64"; + Metric = 256; + Table = "he"; + }; + } + ]; + networkConfig = { + Description = "Hurricane Electrical's 187 /64 unfirewalled zone"; + IPv6SendRA = true; + ConfigureWithoutCarrier = true; + }; + }; + }; + }; + }; +}