From a4844807e652e1ecf63a03a5d34847b3a731f344 Mon Sep 17 00:00:00 2001 From: oddlama Date: Thu, 14 Mar 2024 20:55:08 +0100 Subject: [PATCH] feat: move wireguard module to nixos-extra-modules --- flake.lock | 6 +- hosts/kroma/default.nix | 2 +- hosts/sentinel/default.nix | 3 +- hosts/sentinel/net.nix | 17 +- hosts/sire/default.nix | 2 +- hosts/sire/guests/common.nix | 3 +- hosts/sire/guests/grafana.nix | 7 +- hosts/sire/guests/immich.nix | 7 +- hosts/sire/guests/influxdb.nix | 9 +- hosts/sire/guests/loki.nix | 7 +- hosts/sire/guests/paperless.nix | 11 +- hosts/sire/net.nix | 2 +- hosts/ward/default.nix | 2 +- hosts/ward/guests/adguardhome.nix | 7 +- hosts/ward/guests/common.nix | 3 +- hosts/ward/guests/forgejo.nix | 17 +- hosts/ward/guests/kanidm.nix | 7 +- hosts/ward/guests/radicale.nix | 9 +- hosts/ward/guests/vaultwarden.nix | 9 +- hosts/ward/net.nix | 2 +- hosts/zackbiene/default.nix | 3 +- hosts/zackbiene/home-assistant.nix | 4 +- lib/default.nix | 1 - lib/wireguard.nix | 225 ---------------- modules/default.nix | 2 - modules/wireguard-proxy.nix | 78 ------ modules/wireguard.nix | 411 ----------------------------- 27 files changed, 73 insertions(+), 783 deletions(-) delete mode 100644 lib/wireguard.nix delete mode 100644 modules/wireguard-proxy.nix delete mode 100644 modules/wireguard.nix diff --git a/flake.lock b/flake.lock index 1832069..8e929df 100644 --- a/flake.lock +++ b/flake.lock @@ -950,11 +950,11 @@ "pre-commit-hooks": "pre-commit-hooks_3" }, "locked": { - "lastModified": 1709384560, - "narHash": "sha256-VZpbetW5npjZ1FWcFII81tcDBH03irTboyMVOWzdfF8=", + "lastModified": 1710445839, + "narHash": "sha256-omFKeLtg+OwM8lOGqFak87CGk5mYR/2nfoKmdRs/ELU=", "owner": "oddlama", "repo": "nixos-extra-modules", - "rev": "34ba92f0576a3998133310f070381563448e2b1a", + "rev": "f4d989155419461acd8378e0cfbfa8d5db77f21a", "type": "github" }, "original": { diff --git a/hosts/kroma/default.nix b/hosts/kroma/default.nix index f6f9fb3..bfc184c 100644 --- a/hosts/kroma/default.nix +++ b/hosts/kroma/default.nix @@ -63,7 +63,7 @@ #}; ## Connect safely via wireguard to skip authentication - #networking.hosts.${nodes.sentinel.config.meta.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; + #networking.hosts.${nodes.sentinel.config.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; #meta.telegraf = { # enable = true; # influxdb2 = { diff --git a/hosts/sentinel/default.nix b/hosts/sentinel/default.nix index 911fe27..0c62b50 100644 --- a/hosts/sentinel/default.nix +++ b/hosts/sentinel/default.nix @@ -15,6 +15,7 @@ boot.mode = "bios"; users.groups.acme.members = ["nginx"]; + wireguard.proxy-sentinel.firewallRuleForAll.allowedTCPPorts = [80 443]; services.nginx.enable = true; services.nginx.recommendedSetup = true; @@ -24,7 +25,7 @@ }; # Connect safely via wireguard to skip authentication - networking.hosts.${config.meta.wireguard.proxy-sentinel.ipv4} = [config.networking.providedDomains.influxdb]; + networking.hosts.${config.wireguard.proxy-sentinel.ipv4} = [config.networking.providedDomains.influxdb]; meta.telegraf = { enable = true; scrapeSensors = false; diff --git a/hosts/sentinel/net.nix b/hosts/sentinel/net.nix index 006beda..1dd8be5 100644 --- a/hosts/sentinel/net.nix +++ b/hosts/sentinel/net.nix @@ -34,23 +34,12 @@ }; }; - networking.nftables.firewall = { - zones = { - untrusted.interfaces = ["wan"]; - proxy-sentinel.interfaces = ["proxy-sentinel"]; - }; - # Allow accessing nginx through the proxy - rules.proxy-sentinel-to-local = { - from = ["proxy-sentinel"]; - to = ["local"]; - allowedTCPPorts = [80 443]; - }; - }; + networking.nftables.firewall.zones.untrusted.interfaces = ["wan"]; - meta.wireguard.proxy-sentinel.server = { + wireguard.proxy-sentinel.server = { host = config.networking.fqdn; port = 51443; reservedAddresses = ["10.43.0.0/24" "fd00:43::/120"]; - openFirewallRules = ["untrusted-to-local"]; + openFirewall = true; }; } diff --git a/hosts/sire/default.nix b/hosts/sire/default.nix index 5cd4236..566000e 100644 --- a/hosts/sire/default.nix +++ b/hosts/sire/default.nix @@ -29,7 +29,7 @@ }; # Connect safely via wireguard to skip authentication - networking.hosts.${nodes.sentinel.config.meta.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; + networking.hosts.${nodes.sentinel.config.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; meta.telegraf = { enable = true; influxdb2 = { diff --git a/hosts/sire/guests/common.nix b/hosts/sire/guests/common.nix index 6777f52..c301f6b 100644 --- a/hosts/sire/guests/common.nix +++ b/hosts/sire/guests/common.nix @@ -6,14 +6,13 @@ }: let sentinelCfg = nodes.sentinel.config; in { - meta.wireguard-proxy.sentinel = {}; meta.promtail = { enable = true; proxy = "sentinel"; }; # Connect safely via wireguard to skip http authentication - networking.hosts.${sentinelCfg.meta.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; + networking.hosts.${sentinelCfg.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; meta.telegraf = lib.mkIf (!config.boot.isContainer) { enable = true; scrapeSensors = false; diff --git a/hosts/sire/guests/grafana.nix b/hosts/sire/guests/grafana.nix index 52e8324..bef0d3a 100644 --- a/hosts/sire/guests/grafana.nix +++ b/hosts/sire/guests/grafana.nix @@ -6,7 +6,10 @@ sentinelCfg = nodes.sentinel.config; grafanaDomain = "grafana.${config.repo.secrets.global.domains.me}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.grafana.settings.server.http_port]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.grafana.settings.server.http_port]; + }; age.secrets.grafana-secret-key = { rekeyFile = config.node.secretsDir + "/grafana-secret-key.age"; @@ -58,7 +61,7 @@ in { services.nginx = { upstreams.grafana = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.grafana.settings.server.http_port}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.grafana.settings.server.http_port}" = {}; extraConfig = '' zone grafana 64k; keepalive 2; diff --git a/hosts/sire/guests/immich.nix b/hosts/sire/guests/immich.nix index 3cfa3a8..2de11d7 100644 --- a/hosts/sire/guests/immich.nix +++ b/hosts/sire/guests/immich.nix @@ -165,7 +165,10 @@ in { ''; }; - meta.wireguard-proxy.sentinel.allowedTCPPorts = [2283]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [2283]; + }; networking.nftables.chains.forward.into-immich-container = { after = ["conntrack"]; rules = [ @@ -179,7 +182,7 @@ in { services.nginx = { upstreams.immich = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:2283" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:2283" = {}; extraConfig = '' zone immich 64k; keepalive 2; diff --git a/hosts/sire/guests/influxdb.nix b/hosts/sire/guests/influxdb.nix index f5cb93e..fd7d478 100644 --- a/hosts/sire/guests/influxdb.nix +++ b/hosts/sire/guests/influxdb.nix @@ -9,14 +9,17 @@ influxdbDomain = "influxdb.${config.repo.secrets.global.domains.me}"; influxdbPort = 8086; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [influxdbPort]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [influxdbPort]; + }; nodes.sentinel = { networking.providedDomains.influxdb = influxdbDomain; services.nginx = { upstreams.influxdb = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}" = {}; extraConfig = '' zone influxdb 64k; keepalive 2; @@ -25,7 +28,7 @@ in { virtualHosts.${influxdbDomain} = let accessRules = '' satisfy any; - ${lib.concatMapStrings (ip: "allow ${ip};\n") sentinelCfg.meta.wireguard.proxy-sentinel.server.reservedAddresses} + ${lib.concatMapStrings (ip: "allow ${ip};\n") sentinelCfg.wireguard.proxy-sentinel.server.reservedAddresses} deny all; ''; in { diff --git a/hosts/sire/guests/loki.nix b/hosts/sire/guests/loki.nix index 44a19f7..e9721be 100644 --- a/hosts/sire/guests/loki.nix +++ b/hosts/sire/guests/loki.nix @@ -6,7 +6,10 @@ sentinelCfg = nodes.sentinel.config; lokiDomain = "loki.${config.repo.secrets.global.domains.me}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.loki.configuration.server.http_listen_port]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.loki.configuration.server.http_listen_port]; + }; nodes.sentinel = { networking.providedDomains.loki = lokiDomain; @@ -19,7 +22,7 @@ in { services.nginx = { upstreams.loki = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.loki.configuration.server.http_listen_port}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.loki.configuration.server.http_listen_port}" = {}; extraConfig = '' zone loki 64k; keepalive 2; diff --git a/hosts/sire/guests/paperless.nix b/hosts/sire/guests/paperless.nix index 234fb87..1aec3d1 100644 --- a/hosts/sire/guests/paperless.nix +++ b/hosts/sire/guests/paperless.nix @@ -17,7 +17,7 @@ in { services.nginx = { upstreams.paperless = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.paperless.port}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.paperless.port}" = {}; extraConfig = '' zone paperless 64k; keepalive 2; @@ -38,9 +38,10 @@ in { }; }; - meta.wireguard-proxy.sentinel.allowedTCPPorts = [ - config.services.paperless.port - ]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.paperless.port]; + }; age.secrets.paperless-admin-password = { generator.script = "alnum"; @@ -74,7 +75,7 @@ in { PAPERLESS_URL = "https://${paperlessDomain}"; PAPERLESS_ALLOWED_HOSTS = paperlessDomain; PAPERLESS_CORS_ALLOWED_HOSTS = "https://${paperlessDomain}"; - PAPERLESS_TRUSTED_PROXIES = sentinelCfg.meta.wireguard.proxy-sentinel.ipv4; + PAPERLESS_TRUSTED_PROXIES = sentinelCfg.wireguard.proxy-sentinel.ipv4; # Authentication via kanidm PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect"; diff --git a/hosts/sire/net.nix b/hosts/sire/net.nix index 7015944..530f0c0 100644 --- a/hosts/sire/net.nix +++ b/hosts/sire/net.nix @@ -64,5 +64,5 @@ }; # Allow accessing influx - meta.wireguard.proxy-sentinel.client.via = "sentinel"; + wireguard.proxy-sentinel.client.via = "sentinel"; } diff --git a/hosts/ward/default.nix b/hosts/ward/default.nix index 1d3a224..9537563 100644 --- a/hosts/ward/default.nix +++ b/hosts/ward/default.nix @@ -30,7 +30,7 @@ }; # Connect safely via wireguard to skip authentication - networking.hosts.${nodes.sentinel.config.meta.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; + networking.hosts.${nodes.sentinel.config.wireguard.proxy-sentinel.ipv4} = [nodes.sentinel.config.networking.providedDomains.influxdb]; meta.telegraf = { enable = true; influxdb2 = { diff --git a/hosts/ward/guests/adguardhome.nix b/hosts/ward/guests/adguardhome.nix index a3fe823..879fd6f 100644 --- a/hosts/ward/guests/adguardhome.nix +++ b/hosts/ward/guests/adguardhome.nix @@ -7,14 +7,17 @@ }: let adguardhomeDomain = "adguardhome.${config.repo.secrets.global.domains.me}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [config.services.adguardhome.settings.bind_port]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.adguardhome.settings.bind_port]; + }; nodes.sentinel = { networking.providedDomains.adguard = adguardhomeDomain; services.nginx = { upstreams.adguardhome = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.adguardhome.settings.bind_port}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.adguardhome.settings.bind_port}" = {}; extraConfig = '' zone adguardhome 64k; keepalive 2; diff --git a/hosts/ward/guests/common.nix b/hosts/ward/guests/common.nix index 6777f52..c301f6b 100644 --- a/hosts/ward/guests/common.nix +++ b/hosts/ward/guests/common.nix @@ -6,14 +6,13 @@ }: let sentinelCfg = nodes.sentinel.config; in { - meta.wireguard-proxy.sentinel = {}; meta.promtail = { enable = true; proxy = "sentinel"; }; # Connect safely via wireguard to skip http authentication - networking.hosts.${sentinelCfg.meta.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; + networking.hosts.${sentinelCfg.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; meta.telegraf = lib.mkIf (!config.boot.isContainer) { enable = true; scrapeSensors = false; diff --git a/hosts/ward/guests/forgejo.nix b/hosts/ward/guests/forgejo.nix index d9d881c..9857f9c 100644 --- a/hosts/ward/guests/forgejo.nix +++ b/hosts/ward/guests/forgejo.nix @@ -8,9 +8,10 @@ sentinelCfg = nodes.sentinel.config; forgejoDomain = "git.${config.repo.secrets.global.domains.me}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [ - config.services.forgejo.settings.server.HTTP_PORT - ]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.forgejo.settings.server.HTTP_PORT]; + }; age.secrets.forgejo-mailer-password = { rekeyFile = config.node.secretsDir + "/forgejo-mailer-password.age"; @@ -37,22 +38,22 @@ in { postrouting.to-forgejo = { after = ["hook"]; rules = [ - "iifname wan ip daddr ${config.meta.wireguard.proxy-sentinel.ipv4} tcp dport 22 masquerade random" - "iifname wan ip6 daddr ${config.meta.wireguard.proxy-sentinel.ipv6} tcp dport 22 masquerade random" + "iifname wan ip daddr ${config.wireguard.proxy-sentinel.ipv4} tcp dport 22 masquerade random" + "iifname wan ip6 daddr ${config.wireguard.proxy-sentinel.ipv6} tcp dport 22 masquerade random" ]; }; prerouting.to-forgejo = { after = ["hook"]; rules = [ - "iifname wan tcp dport 9922 dnat ip to ${config.meta.wireguard.proxy-sentinel.ipv4}:22" - "iifname wan tcp dport 9922 dnat ip6 to ${config.meta.wireguard.proxy-sentinel.ipv6}:22" + "iifname wan tcp dport 9922 dnat ip to ${config.wireguard.proxy-sentinel.ipv4}:22" + "iifname wan tcp dport 9922 dnat ip6 to ${config.wireguard.proxy-sentinel.ipv6}:22" ]; }; }; services.nginx = { upstreams.forgejo = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.forgejo.settings.server.HTTP_PORT}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.forgejo.settings.server.HTTP_PORT}" = {}; extraConfig = '' zone forgejo 64k; keepalive 2; diff --git a/hosts/ward/guests/kanidm.nix b/hosts/ward/guests/kanidm.nix index 12b970e..33cffac 100644 --- a/hosts/ward/guests/kanidm.nix +++ b/hosts/ward/guests/kanidm.nix @@ -14,7 +14,10 @@ group = "kanidm"; }; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [kanidmPort]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [kanidmPort]; + }; age.secrets."kanidm-self-signed.crt" = { rekeyFile = config.node.secretsDir + "/kanidm-self-signed.crt.age"; @@ -42,7 +45,7 @@ in { services.nginx = { upstreams.kanidm = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}" = {}; extraConfig = '' zone kanidm 64k; keepalive 2; diff --git a/hosts/ward/guests/radicale.nix b/hosts/ward/guests/radicale.nix index 3c1bdfb..068c01f 100644 --- a/hosts/ward/guests/radicale.nix +++ b/hosts/ward/guests/radicale.nix @@ -1,16 +1,17 @@ {config, ...}: let radicaleDomain = "radicale.${config.repo.secrets.global.domains.personal}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [ - 8000 - ]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [8000]; + }; nodes.sentinel = { networking.providedDomains.radicale = radicaleDomain; services.nginx = { upstreams.radicale = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:8000" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:8000" = {}; extraConfig = '' zone radicale 64k; keepalive 2; diff --git a/hosts/ward/guests/vaultwarden.nix b/hosts/ward/guests/vaultwarden.nix index cd11af1..e1fc551 100644 --- a/hosts/ward/guests/vaultwarden.nix +++ b/hosts/ward/guests/vaultwarden.nix @@ -5,9 +5,10 @@ }: let vaultwardenDomain = "pw.${config.repo.secrets.global.domains.personal}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [ - config.services.vaultwarden.config.rocketPort - ]; + wireguard.proxy-sentinel = { + client.via = "sentinel"; + firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.vaultwarden.config.rocketPort]; + }; age.secrets.vaultwarden-env = { rekeyFile = config.node.secretsDir + "/vaultwarden-env.age"; @@ -29,7 +30,7 @@ in { services.nginx = { upstreams.vaultwarden = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString config.services.vaultwarden.config.rocketPort}" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:${toString config.services.vaultwarden.config.rocketPort}" = {}; extraConfig = '' zone vaultwarden 64k; keepalive 2; diff --git a/hosts/ward/net.nix b/hosts/ward/net.nix index 86f6fbc..27dc583 100644 --- a/hosts/ward/net.nix +++ b/hosts/ward/net.nix @@ -114,5 +114,5 @@ in { }; # Allow accessing influx - meta.wireguard.proxy-sentinel.client.via = "sentinel"; + wireguard.proxy-sentinel.client.via = "sentinel"; } diff --git a/hosts/zackbiene/default.nix b/hosts/zackbiene/default.nix index 205ef69..6399a9d 100644 --- a/hosts/zackbiene/default.nix +++ b/hosts/zackbiene/default.nix @@ -37,14 +37,13 @@ in { }; }; - meta.wireguard-proxy.sentinel = {}; meta.promtail = { enable = true; proxy = "sentinel"; }; # Connect safely via wireguard to skip http authentication - networking.hosts.${sentinelCfg.meta.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; + networking.hosts.${sentinelCfg.wireguard.proxy-sentinel.ipv4} = [sentinelCfg.networking.providedDomains.influxdb]; meta.telegraf = { enable = true; influxdb2 = { diff --git a/hosts/zackbiene/home-assistant.nix b/hosts/zackbiene/home-assistant.nix index f61ca0b..b0474b3 100644 --- a/hosts/zackbiene/home-assistant.nix +++ b/hosts/zackbiene/home-assistant.nix @@ -7,8 +7,6 @@ sentinelCfg = nodes.sentinel.config; homeDomain = "home.${sentinelCfg.repo.secrets.global.domains.personal}"; in { - meta.wireguard-proxy.sentinel.allowedTCPPorts = [80]; - environment.persistence."/persist".directories = [ { directory = config.services.home-assistant.configDir; @@ -145,7 +143,7 @@ in { nodes.sentinel = { services.nginx = { upstreams."zackbiene" = { - servers."${config.meta.wireguard.proxy-sentinel.ipv4}:80" = {}; + servers."${config.wireguard.proxy-sentinel.ipv4}:80" = {}; extraConfig = '' zone zackbiene 64k; keepalive 2; diff --git a/lib/default.nix b/lib/default.nix index 431036a..8505ac0 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -1,4 +1,3 @@ inputs: [ (import ./secrets.nix inputs) - (import ./wireguard.nix inputs) ] diff --git a/lib/wireguard.nix b/lib/wireguard.nix deleted file mode 100644 index 2355909..0000000 --- a/lib/wireguard.nix +++ /dev/null @@ -1,225 +0,0 @@ -inputs: final: prev: let - inherit - (inputs.nixpkgs.lib) - assertMsg - attrNames - attrValues - concatLists - concatMap - concatStringsSep - escapeShellArg - filter - flatten - flip - genAttrs - mapAttrs' - nameValuePair - partition - removeSuffix - ; - - inherit - (final.lib) - net - concatAttrs - types - ; - - inherit - (final.lib.secrets) - rageDecryptArgs - ; - - inherit (inputs.self) nodes; -in { - lib = - prev.lib - // { - wireguard = wgName: let - # Returns the given node's wireguard configuration of this network - wgCfgOf = node: nodes.${node}.config.meta.wireguard.${wgName}; - - sortedPeers = peerA: peerB: - if peerA < peerB - then { - peer1 = peerA; - peer2 = peerB; - } - else { - peer1 = peerB; - peer2 = peerA; - }; - - peerPublicKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.pub"; - peerPublicKeyPath = peerName: inputs.self.outPath + peerPublicKeyFile peerName; - - peerPrivateKeyFile = peerName: "/secrets/wireguard/${wgName}/keys/${peerName}.age"; - peerPrivateKeyPath = peerName: inputs.self.outPath + peerPrivateKeyFile peerName; - peerPrivateKeySecret = peerName: "wireguard-${wgName}-priv-${peerName}"; - - peerPresharedKeyFile = peerA: peerB: let - inherit (sortedPeers peerA peerB) peer1 peer2; - in "/secrets/wireguard/${wgName}/psks/${peer1}+${peer2}.age"; - peerPresharedKeyPath = peerA: peerB: inputs.self.outPath + peerPresharedKeyFile peerA peerB; - peerPresharedKeySecret = peerA: peerB: let - inherit (sortedPeers peerA peerB) peer1 peer2; - in "wireguard-${wgName}-psks-${peer1}+${peer2}"; - - # All nodes that are part of this network - participatingNodes = - filter - (n: builtins.hasAttr wgName nodes.${n}.config.meta.wireguard) - (attrNames nodes); - - # Partition nodes by whether they are servers - _participatingNodes_isServerPartition = - partition - (n: (wgCfgOf n).server.host != null) - participatingNodes; - - participatingServerNodes = _participatingNodes_isServerPartition.right; - participatingClientNodes = _participatingNodes_isServerPartition.wrong; - - # Maps all nodes that are part of this network to their addresses - nodePeers = genAttrs participatingNodes (n: (wgCfgOf n).addresses); - - externalPeerName = p: "external-${p}"; - - # Only peers that are defined as externalPeers on the given node. - # Prepends "external-" to their name. - externalPeersForNode = node: - mapAttrs' (p: nameValuePair (externalPeerName p)) (wgCfgOf node).server.externalPeers; - - # All peers that are defined as externalPeers on any node. - # Prepends "external-" to their name. - allExternalPeers = concatAttrs (map externalPeersForNode participatingNodes); - - # All peers that are part of this network - allPeers = nodePeers // allExternalPeers; - - # Concatenation of all external peer names names without any transformations. - externalPeerNamesRaw = concatMap (n: attrNames (wgCfgOf n).server.externalPeers) participatingNodes; - - # A list of all occurring addresses. - usedAddresses = - concatMap (n: (wgCfgOf n).addresses) participatingNodes - ++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes); - - # A list of all occurring addresses, but only includes addresses that - # are not assigned automatically. - explicitlyUsedAddresses = - flip concatMap participatingNodes - (n: - filter (x: !types.isLazyValue x) - (concatLists - (nodes.${n}.options.meta.wireguard.type.functor.wrapped.getSubOptions (wgCfgOf n)).addresses.definitions)) - ++ flatten (concatMap (n: attrValues (wgCfgOf n).server.externalPeers) participatingNodes); - - # The cidrv4 and cidrv6 of the network spanned by all participating peer addresses. - # This also takes into account any reserved address ranges that should be part of the network. - networkAddresses = - net.cidr.merge (usedAddresses - ++ concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes); - - # The network spanning cidr addresses. The respective cidrv4 and cirdv6 are only - # included if they exist. - networkCidrs = filter (x: x != null) (attrValues networkAddresses); - - # The cidrv4 and cidrv6 of the network spanned by all reserved addresses only. - # Used to determine automatically assigned addresses first. - spannedReservedNetwork = - net.cidr.merge (concatMap (n: (wgCfgOf n).server.reservedAddresses) participatingServerNodes); - - # Assigns an ipv4 address from spannedReservedNetwork.cidrv4 - # to each participant that has not explicitly specified an ipv4 address. - assignedIpv4Addresses = assert assertMsg - (spannedReservedNetwork.cidrv4 != null) - "Wireguard network '${wgName}': At least one participating node must reserve a cidrv4 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network."; - net.cidr.assignIps - spannedReservedNetwork.cidrv4 - # Don't assign any addresses that are explicitly configured on other hosts - (filter (x: net.cidr.contains x spannedReservedNetwork.cidrv4) (filter net.ip.isv4 explicitlyUsedAddresses)) - participatingNodes; - - # Assigns an ipv4 address from spannedReservedNetwork.cidrv4 - # to each participant that has not explicitly specified an ipv4 address. - assignedIpv6Addresses = assert assertMsg - (spannedReservedNetwork.cidrv6 != null) - "Wireguard network '${wgName}': At least one participating node must reserve a cidrv6 address via `reservedAddresses` so that ipv4 addresses can be assigned automatically from that network."; - net.cidr.assignIps - spannedReservedNetwork.cidrv6 - # Don't assign any addresses that are explicitly configured on other hosts - (filter (x: net.cidr.contains x spannedReservedNetwork.cidrv6) (filter net.ip.isv6 explicitlyUsedAddresses)) - participatingNodes; - - # Appends / replaces the correct cidr length to the argument, - # so that the resulting address is in the cidr. - toNetworkAddr = addr: let - relevantNetworkAddr = - if net.ip.isv6 addr - then networkAddresses.cidrv6 - else networkAddresses.cidrv4; - in "${net.cidr.ip addr}/${toString (net.cidr.length relevantNetworkAddr)}"; - - # Creates a script that when executed outputs a wg-quick compatible configuration - # file for use with external peers. This is a script so we can access secrets without - # storing them in the nix-store. - wgQuickConfigScript = system: serverNode: extPeer: let - pkgs = inputs.self.pkgs.${system}; - snCfg = wgCfgOf serverNode; - peerName = externalPeerName extPeer; - addresses = map toNetworkAddr snCfg.server.externalPeers.${extPeer}; - in - pkgs.writeShellScript "create-wg-conf-${wgName}-${serverNode}-${extPeer}" '' - privKey=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPrivateKeyPath peerName)}) \ - || { echo "error: Failed to decrypt!" >&2; exit 1; } - serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \ - || { echo "error: Failed to decrypt!" >&2; exit 1; } - - cat < ((wgCfgOf wgCfg.client.via).server.host != null); - message = "${assertionPrefix}: The specified via node '${wgCfg.client.via}' must be a wireguard server."; - } - { - assertion = stringLength wgCfg.linkName < 16; - message = "${assertionPrefix}: The specified linkName '${wgCfg.linkName}' is too long (must be max 15 characters)."; - } - ]; - - networking.firewall.allowedUDPPorts = - mkIf - (isServer && wgCfg.server.openFirewall) - [wgCfg.server.port]; - - # Open the port in the given nftables rule if specified - networking.nftables.firewall.rules = - optionalAttrs (isServer && wgCfg.server.openFirewallRules != []) - (genAttrs wgCfg.server.openFirewallRules (_: {allowedUDPPorts = [wgCfg.server.port];})); - - age.secrets = - concatAttrs (map - (other: { - ${peerPresharedKeySecret nodeName other} = { - rekeyFile = peerPresharedKeyPath nodeName other; - owner = "systemd-network"; - generator.script = {pkgs, ...}: "${pkgs.wireguard-tools}/bin/wg genpsk"; - }; - }) - neededPeers) - // { - ${peerPrivateKeySecret nodeName} = { - rekeyFile = peerPrivateKeyPath nodeName; - owner = "systemd-network"; - generator.script = { - pkgs, - file, - ... - }: '' - priv=$(${pkgs.wireguard-tools}/bin/wg genkey) - ${pkgs.wireguard-tools}/bin/wg pubkey <<< "$priv" > ${lib.escapeShellArg (lib.removeSuffix ".age" file + ".pub")} - echo "$priv" - ''; - }; - }; - - systemd.network.netdevs."${wgCfg.unitConfName}" = { - netdevConfig = { - Kind = "wireguard"; - Name = wgCfg.linkName; - Description = "Wireguard network ${wgName}"; - }; - wireguardConfig = - { - PrivateKeyFile = config.age.secrets.${peerPrivateKeySecret nodeName}.path; - } - // optionalAttrs isServer { - ListenPort = wgCfg.server.port; - }; - wireguardPeers = - if isServer - then - # Always include all other server nodes. - map (serverNode: let - snCfg = wgCfgOf serverNode; - in { - wireguardPeerConfig = { - PublicKey = builtins.readFile (peerPublicKeyPath serverNode); - PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName serverNode}.path; - AllowedIPs = serverAllowedIPs serverNode; - Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}"; - }; - }) - (filterSelf participatingServerNodes) - # All our external peers - ++ mapAttrsToList (extPeer: ips: let - peerName = externalPeerName extPeer; - in { - wireguardPeerConfig = { - PublicKey = builtins.readFile (peerPublicKeyPath peerName); - PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName peerName}.path; - AllowedIPs = map (net.cidr.make 128) ips; - # Connections to external peers should always be kept alive - PersistentKeepalive = 25; - }; - }) - wgCfg.server.externalPeers - # All client nodes that have their via set to us. - ++ map (clientNode: let - clientCfg = wgCfgOf clientNode; - in { - wireguardPeerConfig = { - PublicKey = builtins.readFile (peerPublicKeyPath clientNode); - PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName clientNode}.path; - AllowedIPs = map (net.cidr.make 128) clientCfg.addresses; - }; - }) - ourClientNodes - else - # We are a client node, so only include our via server. - [ - { - wireguardPeerConfig = let - snCfg = wgCfgOf wgCfg.client.via; - in - { - PublicKey = builtins.readFile (peerPublicKeyPath wgCfg.client.via); - PresharedKeyFile = config.age.secrets.${peerPresharedKeySecret nodeName wgCfg.client.via}.path; - Endpoint = "${snCfg.server.host}:${toString snCfg.server.port}"; - # Access to the whole network is routed through our entry node. - # TODO this should add any routedAddresses on ANY server in the network, right? - # if A entries via B and only C can route 0.0.0.0/0, does that work? - AllowedIPs = networkCidrs; - } - // optionalAttrs wgCfg.client.keepalive { - PersistentKeepalive = 25; - }; - } - ]; - }; - - systemd.network.networks."${wgCfg.unitConfName}" = { - matchConfig.Name = wgCfg.linkName; - address = map toNetworkAddr wgCfg.addresses; - }; - }; -in { - options.meta.wireguard = mkOption { - default = {}; - description = "Configures wireguard networks via systemd-networkd."; - type = types.lazyAttrsOf (types.submodule ({ - config, - name, - options, - ... - }: { - options = { - server = { - host = mkOption { - default = null; - type = types.nullOr types.str; - description = "The hostname or ip address which other peers can use to reach this host. No server funnctionality will be activated if set to null."; - }; - - port = mkOption { - default = 51820; - type = types.port; - description = "The port to listen on."; - }; - - openFirewall = mkOption { - default = false; - type = types.bool; - description = "Whether to open the firewall for the specified {option}`port`."; - }; - - openFirewallRules = mkOption { - default = []; - type = types.listOf types.str; - description = "The {option}`port` will be opened for all of the given rules in the nftable-firewall."; - }; - - externalPeers = mkOption { - type = types.attrsOf (types.listOf (types.net.ip-in config.addresses)); - default = {}; - example = {my-android-phone = ["10.0.0.97"];}; - description = '' - Allows defining an extra set of peers that should be added to this wireguard network, - but will not be managed by this flake. (e.g. phones) - - These external peers will only know this node as a peer, which will forward - their traffic to other members of the network if required. This requires - this node to act as a server. - ''; - }; - - reservedAddresses = mkOption { - type = types.listOf types.net.cidr; - default = []; - example = ["10.0.0.1/24" "fd00:cafe::/64"]; - description = '' - Allows defining extra cidr network ranges that shall be reserved for this network. - Reservation means that those address spaces will be guaranteed to be included in - the spanned network, but no rules will be enforced as to who in the network may use them. - - By default, this module will try to allocate the smallest address space that includes - all network peers. If you know that there might be additional external peers added later, - it may be beneficial to reserve a bigger address space from the start to avoid having - to update existing external peers when the generated address space expands. - ''; - }; - }; - - client = { - via = mkOption { - default = null; - type = types.nullOr types.str; - description = '' - The server node via which to connect to the network. - No client functionality will be activated if set to null. - ''; - }; - - keepalive = mkOption { - default = true; - type = types.bool; - description = "Whether to keep this connection alive using PersistentKeepalive. Set to false only for networks where client and server IPs are stable."; - }; - }; - - priority = mkOption { - default = 40; - type = types.int; - description = "The order priority used when creating systemd netdev and network files."; - }; - - linkName = mkOption { - default = name; - type = types.str; - description = "The name for the created network interface."; - }; - - unitConfName = mkOption { - default = "${toString config.priority}-${config.linkName}"; - readOnly = true; - type = types.str; - description = '' - The name used for unit configuration files. This is a read-only option. - Access this if you want to add additional settings to the generated systemd units. - ''; - }; - - ipv4 = mkOption { - type = types.lazyOf types.net.ipv4; - default = types.lazyValue (wireguard name).assignedIpv4Addresses.${nodeName}; - description = '' - The ipv4 address for this machine. If you do not set this explicitly, - a semi-stable ipv4 address will be derived automatically based on the - hostname of this machine. At least one participating server must reserve - a big-enough space of addresses by setting `reservedAddresses`. - See `net.cidr.assignIps` for more information on the algorithm. - ''; - }; - - ipv6 = mkOption { - type = types.lazyOf types.net.ipv6; - default = types.lazyValue (wireguard name).assignedIpv6Addresses.${nodeName}; - description = '' - The ipv6 address for this machine. If you do not set this explicitly, - a semi-stable ipv6 address will be derived automatically based on the - hostname of this machine. At least one participating server must reserve - a big-enough space of addresses by setting `reservedAddresses`. - See `net.cidr.assignIps` for more information on the algorithm. - ''; - }; - - addresses = mkOption { - type = types.listOf (types.lazyOf types.net.ip); - default = [ - (head options.ipv4.definitions) - (head options.ipv6.definitions) - ]; - description = '' - The ip addresses (v4 and/or v6) to use for this machine. - The actual network cidr will automatically be derived from all network participants. - By default this will just include {option}`ipv4` and {option}`ipv6` as configured. - ''; - }; - - # TODO this is not yet implemented. - # - is 0.0.0.0/0 also for valid for routing global ipv6? - # - is 0.0.0.0/0 routing private spaces such as 192.168.1 ? that'd be baaad - # - force nodes to opt-in or allow nodes to opt-out? sometimes a node wants - # to use the network without routing additional stuff. - # - allow specifying the route metric. - routedAddresses = mkOption { - type = types.listOf types.net.cidr; - default = []; - example = ["0.0.0.0/0"]; - description = '' - Additional networks that are accessible through this machine. This will allow - other participants of the network to access these networks through the tunnel. - - Make sure to configure a NAT on the created interface (or that the proper routes - are generated) to allow inter-network communication. - ''; - }; - }; - })); - }; - - config = mkIf (cfg != {}) (mergeToplevelConfigs - ["assertions" "age" "networking" "systemd"] - (mapAttrsToList configForNetwork cfg)); -}