forked from mirrors_public/oddlama_nix-config
feat: move wireguard module to nixos-extra-modules
This commit is contained in:
parent
621d725af3
commit
a4844807e6
27 changed files with 73 additions and 783 deletions
6
flake.lock
generated
6
flake.lock
generated
|
@ -950,11 +950,11 @@
|
||||||
"pre-commit-hooks": "pre-commit-hooks_3"
|
"pre-commit-hooks": "pre-commit-hooks_3"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1709384560,
|
"lastModified": 1710445839,
|
||||||
"narHash": "sha256-VZpbetW5npjZ1FWcFII81tcDBH03irTboyMVOWzdfF8=",
|
"narHash": "sha256-omFKeLtg+OwM8lOGqFak87CGk5mYR/2nfoKmdRs/ELU=",
|
||||||
"owner": "oddlama",
|
"owner": "oddlama",
|
||||||
"repo": "nixos-extra-modules",
|
"repo": "nixos-extra-modules",
|
||||||
"rev": "34ba92f0576a3998133310f070381563448e2b1a",
|
"rev": "f4d989155419461acd8378e0cfbfa8d5db77f21a",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
#};
|
#};
|
||||||
|
|
||||||
## Connect safely via wireguard to skip authentication
|
## 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 = {
|
#meta.telegraf = {
|
||||||
# enable = true;
|
# enable = true;
|
||||||
# influxdb2 = {
|
# influxdb2 = {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
boot.mode = "bios";
|
boot.mode = "bios";
|
||||||
|
|
||||||
users.groups.acme.members = ["nginx"];
|
users.groups.acme.members = ["nginx"];
|
||||||
|
wireguard.proxy-sentinel.firewallRuleForAll.allowedTCPPorts = [80 443];
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
services.nginx.recommendedSetup = true;
|
services.nginx.recommendedSetup = true;
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip authentication
|
# 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 = {
|
meta.telegraf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
scrapeSensors = false;
|
scrapeSensors = false;
|
||||||
|
|
|
@ -34,23 +34,12 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
networking.nftables.firewall = {
|
networking.nftables.firewall.zones.untrusted.interfaces = ["wan"];
|
||||||
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];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
meta.wireguard.proxy-sentinel.server = {
|
wireguard.proxy-sentinel.server = {
|
||||||
host = config.networking.fqdn;
|
host = config.networking.fqdn;
|
||||||
port = 51443;
|
port = 51443;
|
||||||
reservedAddresses = ["10.43.0.0/24" "fd00:43::/120"];
|
reservedAddresses = ["10.43.0.0/24" "fd00:43::/120"];
|
||||||
openFirewallRules = ["untrusted-to-local"];
|
openFirewall = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip authentication
|
# 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 = {
|
meta.telegraf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
influxdb2 = {
|
influxdb2 = {
|
||||||
|
|
|
@ -6,14 +6,13 @@
|
||||||
}: let
|
}: let
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel = {};
|
|
||||||
meta.promtail = {
|
meta.promtail = {
|
||||||
enable = true;
|
enable = true;
|
||||||
proxy = "sentinel";
|
proxy = "sentinel";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip http authentication
|
# 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) {
|
meta.telegraf = lib.mkIf (!config.boot.isContainer) {
|
||||||
enable = true;
|
enable = true;
|
||||||
scrapeSensors = false;
|
scrapeSensors = false;
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
grafanaDomain = "grafana.${config.repo.secrets.global.domains.me}";
|
grafanaDomain = "grafana.${config.repo.secrets.global.domains.me}";
|
||||||
in {
|
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 = {
|
age.secrets.grafana-secret-key = {
|
||||||
rekeyFile = config.node.secretsDir + "/grafana-secret-key.age";
|
rekeyFile = config.node.secretsDir + "/grafana-secret-key.age";
|
||||||
|
@ -58,7 +61,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.grafana = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone grafana 64k;
|
zone grafana 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -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 = {
|
networking.nftables.chains.forward.into-immich-container = {
|
||||||
after = ["conntrack"];
|
after = ["conntrack"];
|
||||||
rules = [
|
rules = [
|
||||||
|
@ -179,7 +182,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.immich = {
|
upstreams.immich = {
|
||||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:2283" = {};
|
servers."${config.wireguard.proxy-sentinel.ipv4}:2283" = {};
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
zone immich 64k;
|
zone immich 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -9,14 +9,17 @@
|
||||||
influxdbDomain = "influxdb.${config.repo.secrets.global.domains.me}";
|
influxdbDomain = "influxdb.${config.repo.secrets.global.domains.me}";
|
||||||
influxdbPort = 8086;
|
influxdbPort = 8086;
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [influxdbPort];
|
wireguard.proxy-sentinel = {
|
||||||
|
client.via = "sentinel";
|
||||||
|
firewallRuleForNode.sentinel.allowedTCPPorts = [influxdbPort];
|
||||||
|
};
|
||||||
|
|
||||||
nodes.sentinel = {
|
nodes.sentinel = {
|
||||||
networking.providedDomains.influxdb = influxdbDomain;
|
networking.providedDomains.influxdb = influxdbDomain;
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.influxdb = {
|
upstreams.influxdb = {
|
||||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}" = {};
|
servers."${config.wireguard.proxy-sentinel.ipv4}:${toString influxdbPort}" = {};
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
zone influxdb 64k;
|
zone influxdb 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
@ -25,7 +28,7 @@ in {
|
||||||
virtualHosts.${influxdbDomain} = let
|
virtualHosts.${influxdbDomain} = let
|
||||||
accessRules = ''
|
accessRules = ''
|
||||||
satisfy any;
|
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;
|
deny all;
|
||||||
'';
|
'';
|
||||||
in {
|
in {
|
||||||
|
|
|
@ -6,7 +6,10 @@
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
lokiDomain = "loki.${config.repo.secrets.global.domains.me}";
|
lokiDomain = "loki.${config.repo.secrets.global.domains.me}";
|
||||||
in {
|
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 = {
|
nodes.sentinel = {
|
||||||
networking.providedDomains.loki = lokiDomain;
|
networking.providedDomains.loki = lokiDomain;
|
||||||
|
@ -19,7 +22,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.loki = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone loki 64k;
|
zone loki 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -17,7 +17,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.paperless = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone paperless 64k;
|
zone paperless 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
@ -38,9 +38,10 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
wireguard.proxy-sentinel = {
|
||||||
config.services.paperless.port
|
client.via = "sentinel";
|
||||||
];
|
firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.paperless.port];
|
||||||
|
};
|
||||||
|
|
||||||
age.secrets.paperless-admin-password = {
|
age.secrets.paperless-admin-password = {
|
||||||
generator.script = "alnum";
|
generator.script = "alnum";
|
||||||
|
@ -74,7 +75,7 @@ in {
|
||||||
PAPERLESS_URL = "https://${paperlessDomain}";
|
PAPERLESS_URL = "https://${paperlessDomain}";
|
||||||
PAPERLESS_ALLOWED_HOSTS = paperlessDomain;
|
PAPERLESS_ALLOWED_HOSTS = paperlessDomain;
|
||||||
PAPERLESS_CORS_ALLOWED_HOSTS = "https://${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
|
# Authentication via kanidm
|
||||||
PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect";
|
PAPERLESS_APPS = "allauth.socialaccount.providers.openid_connect";
|
||||||
|
|
|
@ -64,5 +64,5 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Allow accessing influx
|
# Allow accessing influx
|
||||||
meta.wireguard.proxy-sentinel.client.via = "sentinel";
|
wireguard.proxy-sentinel.client.via = "sentinel";
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip authentication
|
# 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 = {
|
meta.telegraf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
influxdb2 = {
|
influxdb2 = {
|
||||||
|
|
|
@ -7,14 +7,17 @@
|
||||||
}: let
|
}: let
|
||||||
adguardhomeDomain = "adguardhome.${config.repo.secrets.global.domains.me}";
|
adguardhomeDomain = "adguardhome.${config.repo.secrets.global.domains.me}";
|
||||||
in {
|
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 = {
|
nodes.sentinel = {
|
||||||
networking.providedDomains.adguard = adguardhomeDomain;
|
networking.providedDomains.adguard = adguardhomeDomain;
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.adguardhome = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone adguardhome 64k;
|
zone adguardhome 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -6,14 +6,13 @@
|
||||||
}: let
|
}: let
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel = {};
|
|
||||||
meta.promtail = {
|
meta.promtail = {
|
||||||
enable = true;
|
enable = true;
|
||||||
proxy = "sentinel";
|
proxy = "sentinel";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip http authentication
|
# 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) {
|
meta.telegraf = lib.mkIf (!config.boot.isContainer) {
|
||||||
enable = true;
|
enable = true;
|
||||||
scrapeSensors = false;
|
scrapeSensors = false;
|
||||||
|
|
|
@ -8,9 +8,10 @@
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
forgejoDomain = "git.${config.repo.secrets.global.domains.me}";
|
forgejoDomain = "git.${config.repo.secrets.global.domains.me}";
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
wireguard.proxy-sentinel = {
|
||||||
config.services.forgejo.settings.server.HTTP_PORT
|
client.via = "sentinel";
|
||||||
];
|
firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.forgejo.settings.server.HTTP_PORT];
|
||||||
|
};
|
||||||
|
|
||||||
age.secrets.forgejo-mailer-password = {
|
age.secrets.forgejo-mailer-password = {
|
||||||
rekeyFile = config.node.secretsDir + "/forgejo-mailer-password.age";
|
rekeyFile = config.node.secretsDir + "/forgejo-mailer-password.age";
|
||||||
|
@ -37,22 +38,22 @@ in {
|
||||||
postrouting.to-forgejo = {
|
postrouting.to-forgejo = {
|
||||||
after = ["hook"];
|
after = ["hook"];
|
||||||
rules = [
|
rules = [
|
||||||
"iifname wan ip daddr ${config.meta.wireguard.proxy-sentinel.ipv4} tcp dport 22 masquerade random"
|
"iifname wan ip daddr ${config.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 ip6 daddr ${config.wireguard.proxy-sentinel.ipv6} tcp dport 22 masquerade random"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
prerouting.to-forgejo = {
|
prerouting.to-forgejo = {
|
||||||
after = ["hook"];
|
after = ["hook"];
|
||||||
rules = [
|
rules = [
|
||||||
"iifname wan tcp dport 9922 dnat ip to ${config.meta.wireguard.proxy-sentinel.ipv4}:22"
|
"iifname wan tcp dport 9922 dnat ip to ${config.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 ip6 to ${config.wireguard.proxy-sentinel.ipv6}:22"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.forgejo = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone forgejo 64k;
|
zone forgejo 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -14,7 +14,10 @@
|
||||||
group = "kanidm";
|
group = "kanidm";
|
||||||
};
|
};
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [kanidmPort];
|
wireguard.proxy-sentinel = {
|
||||||
|
client.via = "sentinel";
|
||||||
|
firewallRuleForNode.sentinel.allowedTCPPorts = [kanidmPort];
|
||||||
|
};
|
||||||
|
|
||||||
age.secrets."kanidm-self-signed.crt" = {
|
age.secrets."kanidm-self-signed.crt" = {
|
||||||
rekeyFile = config.node.secretsDir + "/kanidm-self-signed.crt.age";
|
rekeyFile = config.node.secretsDir + "/kanidm-self-signed.crt.age";
|
||||||
|
@ -42,7 +45,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.kanidm = {
|
upstreams.kanidm = {
|
||||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}" = {};
|
servers."${config.wireguard.proxy-sentinel.ipv4}:${toString kanidmPort}" = {};
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
zone kanidm 64k;
|
zone kanidm 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
{config, ...}: let
|
{config, ...}: let
|
||||||
radicaleDomain = "radicale.${config.repo.secrets.global.domains.personal}";
|
radicaleDomain = "radicale.${config.repo.secrets.global.domains.personal}";
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
wireguard.proxy-sentinel = {
|
||||||
8000
|
client.via = "sentinel";
|
||||||
];
|
firewallRuleForNode.sentinel.allowedTCPPorts = [8000];
|
||||||
|
};
|
||||||
|
|
||||||
nodes.sentinel = {
|
nodes.sentinel = {
|
||||||
networking.providedDomains.radicale = radicaleDomain;
|
networking.providedDomains.radicale = radicaleDomain;
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.radicale = {
|
upstreams.radicale = {
|
||||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:8000" = {};
|
servers."${config.wireguard.proxy-sentinel.ipv4}:8000" = {};
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
zone radicale 64k;
|
zone radicale 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -5,9 +5,10 @@
|
||||||
}: let
|
}: let
|
||||||
vaultwardenDomain = "pw.${config.repo.secrets.global.domains.personal}";
|
vaultwardenDomain = "pw.${config.repo.secrets.global.domains.personal}";
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [
|
wireguard.proxy-sentinel = {
|
||||||
config.services.vaultwarden.config.rocketPort
|
client.via = "sentinel";
|
||||||
];
|
firewallRuleForNode.sentinel.allowedTCPPorts = [config.services.vaultwarden.config.rocketPort];
|
||||||
|
};
|
||||||
|
|
||||||
age.secrets.vaultwarden-env = {
|
age.secrets.vaultwarden-env = {
|
||||||
rekeyFile = config.node.secretsDir + "/vaultwarden-env.age";
|
rekeyFile = config.node.secretsDir + "/vaultwarden-env.age";
|
||||||
|
@ -29,7 +30,7 @@ in {
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams.vaultwarden = {
|
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 = ''
|
extraConfig = ''
|
||||||
zone vaultwarden 64k;
|
zone vaultwarden 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -114,5 +114,5 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
# Allow accessing influx
|
# Allow accessing influx
|
||||||
meta.wireguard.proxy-sentinel.client.via = "sentinel";
|
wireguard.proxy-sentinel.client.via = "sentinel";
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,14 +37,13 @@ in {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
meta.wireguard-proxy.sentinel = {};
|
|
||||||
meta.promtail = {
|
meta.promtail = {
|
||||||
enable = true;
|
enable = true;
|
||||||
proxy = "sentinel";
|
proxy = "sentinel";
|
||||||
};
|
};
|
||||||
|
|
||||||
# Connect safely via wireguard to skip http authentication
|
# 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 = {
|
meta.telegraf = {
|
||||||
enable = true;
|
enable = true;
|
||||||
influxdb2 = {
|
influxdb2 = {
|
||||||
|
|
|
@ -7,8 +7,6 @@
|
||||||
sentinelCfg = nodes.sentinel.config;
|
sentinelCfg = nodes.sentinel.config;
|
||||||
homeDomain = "home.${sentinelCfg.repo.secrets.global.domains.personal}";
|
homeDomain = "home.${sentinelCfg.repo.secrets.global.domains.personal}";
|
||||||
in {
|
in {
|
||||||
meta.wireguard-proxy.sentinel.allowedTCPPorts = [80];
|
|
||||||
|
|
||||||
environment.persistence."/persist".directories = [
|
environment.persistence."/persist".directories = [
|
||||||
{
|
{
|
||||||
directory = config.services.home-assistant.configDir;
|
directory = config.services.home-assistant.configDir;
|
||||||
|
@ -145,7 +143,7 @@ in {
|
||||||
nodes.sentinel = {
|
nodes.sentinel = {
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
upstreams."zackbiene" = {
|
upstreams."zackbiene" = {
|
||||||
servers."${config.meta.wireguard.proxy-sentinel.ipv4}:80" = {};
|
servers."${config.wireguard.proxy-sentinel.ipv4}:80" = {};
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
zone zackbiene 64k;
|
zone zackbiene 64k;
|
||||||
keepalive 2;
|
keepalive 2;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
inputs: [
|
inputs: [
|
||||||
(import ./secrets.nix inputs)
|
(import ./secrets.nix inputs)
|
||||||
(import ./wireguard.nix inputs)
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
||||||
serverPsk=$(${pkgs.rage}/bin/rage -d ${rageDecryptArgs} ${escapeShellArg (peerPresharedKeyPath serverNode peerName)}) \
|
|
||||||
|| { echo "[1;31merror:[m Failed to decrypt!" >&2; exit 1; }
|
|
||||||
|
|
||||||
cat <<EOF
|
|
||||||
[Interface]
|
|
||||||
Address = ${concatStringsSep ", " addresses}
|
|
||||||
PrivateKey = $privKey
|
|
||||||
|
|
||||||
[Peer]
|
|
||||||
PublicKey = ${removeSuffix "\n" (builtins.readFile (peerPublicKeyPath serverNode))}
|
|
||||||
PresharedKey = $serverPsk
|
|
||||||
AllowedIPs = ${concatStringsSep ", " networkCidrs}
|
|
||||||
Endpoint = ${snCfg.server.host}:${toString snCfg.server.port}
|
|
||||||
PersistentKeepalive = 25
|
|
||||||
EOF
|
|
||||||
'';
|
|
||||||
in {
|
|
||||||
inherit
|
|
||||||
allExternalPeers
|
|
||||||
allPeers
|
|
||||||
assignedIpv4Addresses
|
|
||||||
assignedIpv6Addresses
|
|
||||||
explicitlyUsedAddresses
|
|
||||||
externalPeerName
|
|
||||||
externalPeerNamesRaw
|
|
||||||
externalPeersForNode
|
|
||||||
networkAddresses
|
|
||||||
networkCidrs
|
|
||||||
nodePeers
|
|
||||||
participatingClientNodes
|
|
||||||
participatingNodes
|
|
||||||
participatingServerNodes
|
|
||||||
peerPresharedKeyFile
|
|
||||||
peerPresharedKeyPath
|
|
||||||
peerPresharedKeySecret
|
|
||||||
peerPrivateKeyFile
|
|
||||||
peerPrivateKeyPath
|
|
||||||
peerPrivateKeySecret
|
|
||||||
peerPublicKeyFile
|
|
||||||
peerPublicKeyPath
|
|
||||||
sortedPeers
|
|
||||||
spannedReservedNetwork
|
|
||||||
toNetworkAddr
|
|
||||||
usedAddresses
|
|
||||||
wgCfgOf
|
|
||||||
wgQuickConfigScript
|
|
||||||
;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -37,8 +37,6 @@
|
||||||
./provided-domains.nix
|
./provided-domains.nix
|
||||||
./secrets.nix
|
./secrets.nix
|
||||||
./telegraf.nix
|
./telegraf.nix
|
||||||
./wireguard-proxy.nix
|
|
||||||
./wireguard.nix
|
|
||||||
|
|
||||||
../topology/module.nix
|
../topology/module.nix
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
nodes,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit
|
|
||||||
(lib)
|
|
||||||
attrNames
|
|
||||||
flip
|
|
||||||
mkIf
|
|
||||||
mkMerge
|
|
||||||
mkOption
|
|
||||||
types
|
|
||||||
;
|
|
||||||
|
|
||||||
cfg = config.meta.wireguard-proxy;
|
|
||||||
in {
|
|
||||||
options.meta.wireguard-proxy = mkOption {
|
|
||||||
default = {};
|
|
||||||
description = ''
|
|
||||||
Each entry here will setup a wireguard network that connects via the
|
|
||||||
given node and adds appropriate firewall zones. There will be a zone for
|
|
||||||
the interface and one for the proxy server specifically. A corresponding
|
|
||||||
rule `''${name}-to-local` will be created to easily expose services to the proxy.
|
|
||||||
'';
|
|
||||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
|
||||||
options = {
|
|
||||||
nicName = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "proxy-${name}";
|
|
||||||
description = "The name for the created wireguard network and its interface";
|
|
||||||
};
|
|
||||||
allowedTCPPorts = mkOption {
|
|
||||||
type = types.listOf types.int;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to allow incoming TCP connections from the proxy server (just the server, not the entire network).";
|
|
||||||
};
|
|
||||||
allowedUDPPorts = mkOption {
|
|
||||||
type = types.listOf types.int;
|
|
||||||
default = [];
|
|
||||||
description = "Convenience option to allow incoming UDP connections from the proxy server (just the server, not the entire network).";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
config = mkIf (cfg != {}) {
|
|
||||||
meta.wireguard = mkMerge (flip map (attrNames cfg) (proxy: {
|
|
||||||
${cfg.${proxy}.nicName}.client.via = proxy;
|
|
||||||
}));
|
|
||||||
|
|
||||||
networking.nftables.firewall = mkMerge (flip map (attrNames cfg) (proxy: {
|
|
||||||
zones = {
|
|
||||||
# Parent zone for the whole interface
|
|
||||||
${cfg.${proxy}.nicName}.interfaces = [cfg.${proxy}.nicName];
|
|
||||||
# Subzone to specifically target the proxy host
|
|
||||||
${proxy} = {
|
|
||||||
parent = cfg.${proxy}.nicName;
|
|
||||||
ipv4Addresses = [nodes.${proxy}.config.meta.wireguard.${cfg.${proxy}.nicName}.ipv4];
|
|
||||||
ipv6Addresses = [nodes.${proxy}.config.meta.wireguard.${cfg.${proxy}.nicName}.ipv6];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
rules."${proxy}-to-local" = {
|
|
||||||
from = [proxy];
|
|
||||||
to = ["local"];
|
|
||||||
ignoreEmptyRule = true;
|
|
||||||
|
|
||||||
inherit
|
|
||||||
(cfg.${proxy})
|
|
||||||
allowedTCPPorts
|
|
||||||
allowedUDPPorts
|
|
||||||
;
|
|
||||||
};
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,411 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}: let
|
|
||||||
inherit
|
|
||||||
(lib)
|
|
||||||
any
|
|
||||||
attrNames
|
|
||||||
attrValues
|
|
||||||
concatAttrs
|
|
||||||
concatMap
|
|
||||||
concatMapStrings
|
|
||||||
concatStringsSep
|
|
||||||
duplicates
|
|
||||||
filter
|
|
||||||
genAttrs
|
|
||||||
head
|
|
||||||
mapAttrsToList
|
|
||||||
mergeToplevelConfigs
|
|
||||||
mkIf
|
|
||||||
mkOption
|
|
||||||
net
|
|
||||||
optionalAttrs
|
|
||||||
optionals
|
|
||||||
stringLength
|
|
||||||
types
|
|
||||||
wireguard
|
|
||||||
;
|
|
||||||
|
|
||||||
cfg = config.meta.wireguard;
|
|
||||||
nodeName = config.node.name;
|
|
||||||
|
|
||||||
configForNetwork = wgName: wgCfg: let
|
|
||||||
inherit
|
|
||||||
(wireguard wgName)
|
|
||||||
externalPeerName
|
|
||||||
externalPeerNamesRaw
|
|
||||||
networkCidrs
|
|
||||||
participatingClientNodes
|
|
||||||
participatingNodes
|
|
||||||
participatingServerNodes
|
|
||||||
peerPresharedKeyPath
|
|
||||||
peerPresharedKeySecret
|
|
||||||
peerPrivateKeyPath
|
|
||||||
peerPrivateKeySecret
|
|
||||||
peerPublicKeyPath
|
|
||||||
toNetworkAddr
|
|
||||||
usedAddresses
|
|
||||||
wgCfgOf
|
|
||||||
;
|
|
||||||
|
|
||||||
isServer = wgCfg.server.host != null;
|
|
||||||
isClient = wgCfg.client.via != null;
|
|
||||||
filterSelf = filter (x: x != nodeName);
|
|
||||||
|
|
||||||
# All nodes that use our node as the via into the wireguard network
|
|
||||||
ourClientNodes =
|
|
||||||
optionals isServer
|
|
||||||
(filter (n: (wgCfgOf n).client.via == nodeName) participatingClientNodes);
|
|
||||||
|
|
||||||
# The list of peers for which we have to know the psk.
|
|
||||||
neededPeers =
|
|
||||||
if isServer
|
|
||||||
then
|
|
||||||
# Other servers in the same network
|
|
||||||
filterSelf participatingServerNodes
|
|
||||||
# Our external peers
|
|
||||||
++ map externalPeerName (attrNames wgCfg.server.externalPeers)
|
|
||||||
# Our clients
|
|
||||||
++ ourClientNodes
|
|
||||||
else [wgCfg.client.via];
|
|
||||||
|
|
||||||
# Figure out if there are duplicate peers or addresses so we can
|
|
||||||
# make an assertion later.
|
|
||||||
duplicatePeers = duplicates externalPeerNamesRaw;
|
|
||||||
duplicateAddrs = duplicates usedAddresses;
|
|
||||||
|
|
||||||
# Adds context information to the assertions for this network
|
|
||||||
assertionPrefix = "Wireguard network '${wgName}' on '${nodeName}'";
|
|
||||||
|
|
||||||
# Calculates the allowed ips for another server from our perspective.
|
|
||||||
# Usually we just want to allow other peers to route traffic
|
|
||||||
# for our "children" through us, additional to traffic to us of course.
|
|
||||||
# If a server exposes additional network access (global, lan, ...),
|
|
||||||
# these can be added aswell.
|
|
||||||
# TODO (do that)
|
|
||||||
serverAllowedIPs = serverNode: let
|
|
||||||
snCfg = wgCfgOf serverNode;
|
|
||||||
in
|
|
||||||
map (net.cidr.make 128) (
|
|
||||||
# The server accepts traffic to it's own address
|
|
||||||
snCfg.addresses
|
|
||||||
# plus traffic for any of its external peers
|
|
||||||
++ attrValues snCfg.server.externalPeers
|
|
||||||
# plus traffic for any client that is connected via that server
|
|
||||||
++ concatMap (n: (wgCfgOf n).addresses) (filter (n: (wgCfgOf n).client.via == serverNode) participatingClientNodes)
|
|
||||||
);
|
|
||||||
in {
|
|
||||||
assertions = [
|
|
||||||
{
|
|
||||||
assertion = any (n: (wgCfgOf n).server.host != null) participatingNodes;
|
|
||||||
message = "${assertionPrefix}: At least one node in a network must be a server.";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = duplicatePeers == [];
|
|
||||||
message = "${assertionPrefix}: Multiple definitions for external peer(s):${concatMapStrings (x: " '${x}'") duplicatePeers}";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = duplicateAddrs == [];
|
|
||||||
message = "${assertionPrefix}: Addresses used multiple times: ${concatStringsSep ", " duplicateAddrs}";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = isServer != isClient;
|
|
||||||
message = "${assertionPrefix}: A node must either be a server (define server.host) or a client (define client.via).";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assertion = isClient -> ((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));
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue