Merge pull request 'simplified-mesh' (#1) from simplified-mesh into main

Reviewed-on: #1
This commit is contained in:
asyncnomi 2025-07-23 21:46:37 +02:00
commit a95436771c
18 changed files with 103 additions and 145 deletions

View file

@ -308,7 +308,7 @@
bastion-ren-lasuite-federez = { bastion-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 14; id = 1;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -331,7 +331,7 @@
db-ren-lasuite-federez = { db-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 15; id = 2;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -354,7 +354,7 @@
dns-ren-lasuite-federez = { dns-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 16; id = 3;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -377,7 +377,7 @@
docs-ren-lasuite-federez = { docs-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 17; id = 4;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -400,7 +400,7 @@
meet-ren-lasuite-federez = { meet-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 18; id = 5;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -423,7 +423,7 @@
gris-ren-lasuite-federez = { gris-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 19; id = 6;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -446,7 +446,7 @@
mail-ren-lasuite-federez = { mail-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 20; id = 7;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -469,7 +469,7 @@
garage-ren-lasuite-federez = { garage-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 21; id = 8;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -492,7 +492,7 @@
livekit-ren-lasuite-federez = { livekit-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 22; id = 9;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -515,7 +515,7 @@
backup-ren-lasuite-federez = { backup-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 23; id = 10;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -538,7 +538,7 @@
prom-ren-lasuite-federez = { prom-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 24; id = 11;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [
@ -561,7 +561,7 @@
auth-ren-lasuite-federez = { auth-ren-lasuite-federez = {
zone = 2; zone = 2;
id = 25; id = 12;
system = "x86_64-linux"; system = "x86_64-linux";
ver = "25.05"; ver = "25.05";
modules = [ modules = [

View file

@ -57,18 +57,18 @@ in
"wireguard/wg-private-zone-1-id-13.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-1-id-13.age".publicKeys = system-wg ++ users;
# Zone 2 (Rennes) # Zone 2 (Rennes)
"wireguard/wg-private-zone-2-id-14.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-1.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-15.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-2.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-16.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-3.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-17.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-4.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-18.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-5.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-19.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-6.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-20.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-7.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-21.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-8.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-22.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-9.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-23.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-10.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-24.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-11.age".publicKeys = system-wg ++ users;
"wireguard/wg-private-zone-2-id-25.age".publicKeys = system-wg ++ users; "wireguard/wg-private-zone-2-id-12.age".publicKeys = system-wg ++ users;
# Bastion secrets # Bastion secrets

View file

@ -12,36 +12,44 @@ let
buildSecret = zone: { buildSecret = zone: {
"wg-private-zone-${toString zone}" = { "wg-private-zone-${toString zone}" = {
file = ./../../secrets/bastion + ( "/wg-private-zone-" + toString zone + ".age" ); file = ./../../secrets/bastion + ( "/wg-private-zone-" + toString zone + ".age" );
owner = "root"; owner = "systemd-network";
group = "root"; group = "systemd-network";
}; };
}; };
peers = map (peerConfig: { interfacePeers = map (peerConfig: {
name = "${peerConfig.name}"; PublicKey = peerConfig.publicKey;
publicKey = peerConfig.publicKey; AllowedIPs = [
allowedIPs = [
"172.19.${toString (myZone + 127)}.${toString peerConfig.id}/32" "172.19.${toString (myZone + 127)}.${toString peerConfig.id}/32"
"fc00:f::${toString (myZone + 127)}:${toString peerConfig.id}/128" "fc00:f::${toString (myZone + 127)}:${toString peerConfig.id}/128"
]; ];
persistentKeepalive = 25; PersistentKeepalive = 25;
}) users-wg; }) users-wg;
interface = { interfaceConfig = {
"mgmt" = { PrivateKeyFile = config.age.secrets."wg-private-zone-${toString myZone}".path;
ips = [ ListenPort = 51920;
"172.19.${toString (myZone + 127)}.254/24"
"fc00:f::${toString (myZone + 127)}:254/96"
];
privateKeyFile = config.age.secrets."wg-private-zone-${toString myZone}".path;
listenPort = 51920;
peers = peers;
};
}; };
in in
{ {
age.secrets = buildSecret myZone; age.secrets = buildSecret myZone;
# Return all WireGuard interfaces for each node # Build Management interface
networking.wireguard.interfaces = interface; systemd.network = {
netdevs."mgmt" = {
netdevConfig = {
Name = "mgmt";
Kind = "wireguard";
};
wireguardConfig = interfaceConfig;
wireguardPeers = interfacePeers;
};
networks."mgmt" = {
matchConfig.Name = "mgmt";
address = [
"172.19.${toString (myZone + 127)}.254/24"
"fc00:f::${toString (myZone + 127)}:254/96"
];
};
};
} }

View file

@ -3,7 +3,8 @@
let let
# Import nodes # Import nodes
nodes = import ./../../nodes.nix; nodes = import ./../../nodes.nix;
myPeer = nodes."${config.hostName}"; myName = config.hostName;
myPeer = nodes."${myName}";
myId = myPeer.id; myId = myPeer.id;
myZone = myPeer.zone; myZone = myPeer.zone;
@ -13,107 +14,66 @@ let
buildSecret = zone: id: { buildSecret = zone: id: {
"wg-private-zone-${toString zone}-id-${toString id}" = { "wg-private-zone-${toString zone}-id-${toString id}" = {
file = ./../../secrets/wireguard + ( "/wg-private-zone-" + toString zone + "-id-" + toString id + ".age" ); file = ./../../secrets/wireguard + ( "/wg-private-zone-" + toString zone + "-id-" + toString id + ".age" );
owner = "root"; owner = "systemd-network";
group = "root"; group = "systemd-network";
}; };
}; };
generatedSecrets = lib.mapAttrsToList (name: node: buildSecret node.zone node.id) nodes; generatedSecrets = lib.mapAttrsToList (name: node: buildSecret node.zone node.id) nodes;
shorten = peerName: let
parts = lib.splitString "-" peerName;
shortened = lib.concatStrings (map (part: lib.substring 0 1 part) parts);
in shortened;
# Filter itself out of the peer list # Filter itself out of the peer list
peerConfigs = lib.filterAttrs (_peerName: peerConfig: peerConfig.id != myId) nodes; peerConfigs = lib.filterAttrs (peerName: _peerConfig: peerName != myName) nodes;
interfacePeers = lib.mapAttrsToList (peerName: peerConfig: {
PublicKey = peerConfig.wg-pub;
AllowedIPs = [
"172.19.${toString peerConfig.zone}.${toString peerConfig.id}/32"
"fc00::${toString peerConfig.zone}:${toString peerConfig.id}/128"
] ++ lib.optionals (lib.elem peerName mapping.bastion) [
"172.19.${toString (peerConfig.zone + 127)}.0/24"
"fc00:f::${toString (peerConfig.zone + 127)}:0/112"
];
Endpoint = "${builtins.head (builtins.split "/" peerConfig.ip4)}:51820";
PersistentKeepalive = 25;
}) peerConfigs;
interfaceConfig = {
PrivateKeyFile = config.age.secrets."wg-private-zone-${toString myZone}-id-${toString myId}".path;
ListenPort = 51820;
};
# We'll make one if per peer, this is more flexible # Return route for mgmt traffic
interfacePeers = lib.flatten (lib.mapAttrsToList (peerName: peerConfig: let bastionConfigs = lib.filterAttrs (peerName: _peerConfig: lib.elem peerName mapping.bastion) peerConfigs;
remoteId = peerConfig.id;
remoteZone = peerConfig.zone;
# The mesh is for now only IPv4 based rtwg4 = map (node: {
if4 = { Gateway = "172.19.${toString node.zone}.${toString node.id}";
"mesh-${shorten peerName}-${toString remoteZone}-${toString remoteId}" = { Destination = "172.19.${toString (node.zone + 127)}.0/24";
ips = [ }) (lib.attrValues bastionConfigs);
"172.19.0.${toString myId}/32"
"172.19.${toString myId}.${toString remoteId}/32" rtwg6 = map (node: {
"fc00::${toString myId}/128" Gateway = "fc00::${toString node.zone}:${toString node.id}";
"fc00::${toString myId}:${toString remoteId}/128" Destination = "fc00:f::${toString (node.zone + 127)}:0/112";
]; }) (lib.attrValues bastionConfigs);
privateKeyFile = config.age.secrets."wg-private-zone-${toString myZone}-id-${toString myId}".path;
listenPort = 51000 + remoteId;
peers = [{
name = "${peerName}-ip4";
publicKey = peerConfig.wg-pub;
allowedIPs = [
# Allow mesh trafic
"172.19.0.${toString peerConfig.id}/32"
"172.19.${toString peerConfig.id}.0/24"
"fc00::${toString peerConfig.id}:0/96"
# Allow mgmt transport
"172.19.128.0/17"
"fc00:f::/96"
];
endpoint = "${builtins.head (builtins.split "/" peerConfig.ip4)}:${toString (51000 + myId)}";
persistentKeepalive = 25;
}];
# Throw away route created by wireguard
table = "off";
};
};
in if4) peerConfigs);
wireguardInterfaces = builtins.foldl' (acc: set: acc // set) {} interfacePeers;
generateRoute = peerName: peerConfig: ''
# Static route to declare static wireguard peer inner tunnel ip (that has been thrown away to the "off" table)
${pkgs.iproute2}/bin/ip route replace 172.19.${toString peerConfig.id}.0/24 dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id} scope link
${pkgs.iproute2}/bin/ip -6 route replace fc00::${toString peerConfig.id}:0/112 dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id} scope link
# Nodes self ip
${pkgs.iproute2}/bin/ip route replace 172.19.0.${toString peerConfig.id}/32 dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id} scope link
${pkgs.iproute2}/bin/ip -6 route replace fc00::${toString peerConfig.id}/128 dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id} scope link
# Return path for mgmt trafic
${if lib.elem peerName mapping.bastion then ''
${pkgs.iproute2}/bin/ip route replace 172.19.${toString (peerConfig.zone + 127)}.0/24 via 172.19.${toString peerConfig.id}.${toString myId} dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id}
${pkgs.iproute2}/bin/ip -6 route replace fc00:f::${toString (peerConfig.zone + 127)}:0/112 via fc00::${toString peerConfig.id}:${toString myId} dev mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id}
'' else ""}
'';
generateAfter = peerName: peerConfig: "wireguard-mesh-${shorten peerName}-${toString peerConfig.zone}-${toString peerConfig.id}.target";
routes = lib.mapAttrsToList (name: peer: generateRoute name peer) peerConfigs;
afters = lib.mapAttrsToList (name: peer: generateAfter name peer) peerConfigs;
wireguardStaticRoute = pkgs.writeShellScriptBin "wireguardStaticRoute" ''
${lib.concatStrings routes}
'';
in in
{ {
age.secrets = lib.lists.foldl' (acc: set: lib.attrsets.recursiveUpdate acc set) {} generatedSecrets; age.secrets = lib.lists.foldl' (acc: set: lib.attrsets.recursiveUpdate acc set) {} generatedSecrets;
# Networkd backend introduce in 25.05 # Build Mesh interface
# No independant target are generated systemd.network = {
# when using networkd as a backend netdevs."mesh" = {
# We need them, see below netdevConfig = {
networking.wireguard.useNetworkd = false; Name = "mesh";
Kind = "wireguard";
# Return all WireGuard interfaces for each node };
networking.wireguard.interfaces = wireguardInterfaces; wireguardConfig = interfaceConfig;
wireguardPeers = interfacePeers;
# Execute custom routing for wireguard };
systemd.services.wireguardStaticRouting = { networks."mesh" = {
wantedBy = [ "multi-user.target" ]; matchConfig.Name = "mesh";
# Thus we need specific target to be reached to run it address = [
after = afters; "172.19.${toString myZone}.${toString myId}/17"
description = "Add custom ip route for wireguard."; "fc00::${toString myZone}:${toString myId}/112"
serviceConfig = { ];
Type = "oneshot"; routes = rtwg4 ++ rtwg6;
User = "root";
ExecStart = "${wireguardStaticRoute}/bin/wireguardStaticRoute";
}; };
}; };
} }

View file

@ -35,11 +35,5 @@ in
linkConfig.RequiredForOnline = "routable"; linkConfig.RequiredForOnline = "routable";
}; };
}; };
config.addRouteTablesToIPRoute2 = true;
config.routeTables = {
# Act as a route bin
off = 999;
};
}; };
} }

View file

@ -7,10 +7,6 @@ let
# Import mapping # Import mapping
mapping = import ./../../mapping.nix; mapping = import ./../../mapping.nix;
meshUdpRange = "51000-${toString (51000 + builtins.head (
builtins.sort (a: b: a > b) (
lib.mapAttrsToList (name: node: node.id) nodes)))}";
in in
{ {
networking = { networking = {
@ -50,7 +46,7 @@ in
tcp dport 22 accept tcp dport 22 accept
# Mesh # Mesh
udp dport ${meshUdpRange} accept udp dport 51820 accept
${if lib.elem myName mapping.bastion then '' ${if lib.elem myName mapping.bastion then ''
# Mgmt # Mgmt