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.
This commit is contained in:
Raito Bezarius 2024-01-12 02:22:05 +01:00
parent a0681ee841
commit 1a6f9ffb8f
3 changed files with 351 additions and 332 deletions

View file

@ -1,341 +1,36 @@
{ config, pkgs, lib, ... }: { 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 = { krz-router = {
wan = { enable = true;
vip = mkOption { enablePrimary = true;
type = types.str; vip = "129.199.146.230";
description = "Highly-available virtual IP address of the router"; rip = "129.199.146.231";
}; trunkPort.macAddress = "92:E3:9C:CE:EF:14";
rip = mkOption {
type = types.str;
description = "Real IP address of the router";
};
};
}; };
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.enable = true;
# services.keepalived.vrrpInstances.wan = { # services.keepalived.vrrpInstances.wan = {
# interface = "swp"; # interface = "swp";
# state = "MASTER"; # state = "MASTER";
# priority = 50; # priority = 50;
# virtualIps = [{ addr = "129.199.146.230"; }]; # virtualIps = [{ addr = "129.199.146.230"; }];
# virtualRouterId = 1; # virtualRouterId = 1;
# }; # };
# systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug"; # systemd.services."systemd-networkd".environment.SYSTEMD_LOG_LEVEL = "debug";
environment.systemPackages = [ pkgs.tcpdump pkgs.wireguard-tools ]; environment.systemPackages = [ pkgs.tcpdump pkgs.wireguard-tools ];
# Zone based firewall # Zone based firewall
# Flow accounting in PostgreSQL. # Flow accounting in PostgreSQL.
services.postgresql = { services.postgresql = {
enable = true; enable = true;
ensureUsers = []; ensureUsers = [];
}; };
# services.ulogd = { # services.ulogd = {
# enable = true; # enable = true;
# settings = { # settings = {
# }; # };
# }; # };
};
} }

View file

@ -4,6 +4,7 @@
imports = (lib.extra.mkImports ./. [ imports = (lib.extra.mkImports ./. [
"krz-access-control" "krz-access-control"
"krz-ssh" "krz-ssh"
"krz-router"
]) ++ [ ]) ++ [
# TODO: Switch to global version of agenix via npins # TODO: Switch to global version of agenix via npins
# "${sources.agenix}/modules/age.nix" # "${sources.agenix}/modules/age.nix"

323
modules/krz-router.nix Normal file
View file

@ -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;
};
};
};
};
};
}