summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--assets/vpn.nix33
-rw-r--r--machines/taupe/default.nix5
-rw-r--r--machines/taureau/default.nix5
-rw-r--r--machines/truite/default.nix5
-rw-r--r--modules/foundation/default.nix1
-rw-r--r--modules/foundation/vpn.nix90
-rw-r--r--modules/tunnel/definition.nix31
-rw-r--r--modules/tunnel/ingress.nix6
-rw-r--r--secrets/secrets.nix8
-rw-r--r--secrets/wg-private-key.age (renamed from secrets/tunnel/ingress-key.age)0
10 files changed, 148 insertions, 36 deletions
diff --git a/assets/vpn.nix b/assets/vpn.nix
new file mode 100644
index 0000000..e30438b
--- /dev/null
+++ b/assets/vpn.nix
@@ -0,0 +1,33 @@
+# shared vpn definitions for wireguard, used by both the tunnel
+# and the plain vpn module.
+
+{
+ # the public key of the shared wireguard key used by all vpn hosts.
+ # the matching private key is the secret `wg-private-key`.
+ public = "s5yyPCJiN0uqW0jzKIbYCF7I9TthymiRzpNt466XeWk=";
+
+ # users allowed to connect to any wireguard-based service.
+ # their ip is always a template, with 'X' representing the
+ # network subnet they are connecting to.
+ users = {
+ mel = {
+ key = "vnZoHXapCLLUhZ8A8R5W0iJ8LpWVLve29z41kkoT0BU=";
+ ip = "10.123.X.101";
+ };
+
+ andrei = {
+ key = "qqU4uYImLfUohIwl4KBshPtTINFcs0JVALjbmwpfxRg=";
+ ip = "10.123.X.102";
+ };
+
+ sergo = {
+ key = "qbZGMNIDZFCJC6SHtlyNIlIdGWHELceXClJCcagrj2Y=";
+ ip = "10.123.X.103";
+ };
+
+ fedor = {
+ key = "tEO9r8+jTpu8TBRmZ+/v087IgD/QfmofLUKs249i/F0=";
+ ip = "10.123.X.104";
+ };
+ };
+}
diff --git a/machines/taupe/default.nix b/machines/taupe/default.nix
index 51d84b4..1c69254 100644
--- a/machines/taupe/default.nix
+++ b/machines/taupe/default.nix
@@ -22,6 +22,11 @@
};
monitoring.client.enable = false;
+
+ vpn = {
+ enable = true;
+ externalInterface = "enp1s0";
+ };
};
system.stateVersion = "25.05";
diff --git a/machines/taureau/default.nix b/machines/taureau/default.nix
index d9d5636..ef234c5 100644
--- a/machines/taureau/default.nix
+++ b/machines/taureau/default.nix
@@ -22,6 +22,11 @@
};
monitoring.client.enable = false;
+
+ vpn = {
+ enable = true;
+ externalInterface = "enp6s16";
+ };
};
system.stateVersion = "25.05";
diff --git a/machines/truite/default.nix b/machines/truite/default.nix
index 6b08235..4cfec25 100644
--- a/machines/truite/default.nix
+++ b/machines/truite/default.nix
@@ -27,6 +27,11 @@
"tailnet"
];
};
+
+ vpn = {
+ enable = true;
+ externalInterface = "ens18";
+ };
};
system.stateVersion = "25.11";
diff --git a/modules/foundation/default.nix b/modules/foundation/default.nix
index 3905eb8..fa84750 100644
--- a/modules/foundation/default.nix
+++ b/modules/foundation/default.nix
@@ -6,5 +6,6 @@
./services
./monitoring
./www
+ ./vpn.nix
];
}
diff --git a/modules/foundation/vpn.nix b/modules/foundation/vpn.nix
new file mode 100644
index 0000000..1a7524d
--- /dev/null
+++ b/modules/foundation/vpn.nix
@@ -0,0 +1,90 @@
+{
+ config,
+ lib,
+ ...
+}:
+
+let
+ inherit (lib)
+ mkEnableOption
+ mkOption
+ types
+ mkIf
+ attrValues
+ replaceString
+ ;
+
+ cfg = config.foundation.vpn;
+
+ inherit (import ../../assets/vpn.nix) users;
+
+ interface = "vpn0";
+ port = 51820;
+
+ # while the tunnel uses the lower subnets for the paths,
+ # the vpn always uses subnet number 10.
+ subnetIndex = 10;
+
+ addressFromTemplate =
+ template: prefix: "${replaceString "X" (toString subnetIndex) template}/${toString prefix}";
+in
+{
+ options.foundation.vpn = {
+ enable = mkEnableOption "WireGuard VPN server";
+
+ externalInterface = mkOption {
+ type = types.str;
+ description = "External network interface";
+ };
+ };
+
+ config = mkIf cfg.enable {
+ age.secrets.wg-private-key = {
+ file = ../../secrets/wg-private-key.age;
+ owner = "systemd-network";
+ };
+
+ networking.firewall.allowedUDPPorts = [ port ];
+
+ systemd.network = {
+ netdevs."30-${interface}" = {
+ netdevConfig = {
+ Kind = "wireguard";
+ Name = interface;
+ };
+ wireguardConfig = {
+ PrivateKeyFile = config.age.secrets.wg-private-key.path;
+ ListenPort = port;
+ };
+ wireguardPeers = map (user: {
+ PublicKey = user.key;
+ AllowedIPs = [ (addressFromTemplate user.ip 32) ];
+ }) (attrValues users);
+ };
+
+ networks."30-${interface}" = {
+ name = interface;
+ address = [ (addressFromTemplate "10.123.X.1" 24) ];
+ linkConfig = {
+ RequiredForOnline = "no";
+ };
+ };
+ };
+
+ networking.nftables.tables.vpn-nat = {
+ family = "ip";
+ content = ''
+ chain postrouting {
+ type nat hook postrouting priority srcnat; policy accept;
+ iifname "${interface}" oifname "${cfg.externalInterface}" masquerade
+ }
+
+ chain forward {
+ type filter hook forward priority 0; policy accept;
+ iifname "${interface}" oifname "${cfg.externalInterface}" accept
+ iifname "${cfg.externalInterface}" oifname "${interface}" ct state established,related accept
+ }
+ '';
+ };
+ };
+}
diff --git a/modules/tunnel/definition.nix b/modules/tunnel/definition.nix
index 74ae268..82534bf 100644
--- a/modules/tunnel/definition.nix
+++ b/modules/tunnel/definition.nix
@@ -36,40 +36,11 @@
}
];
- # there are our users who are allowed to connect to any of our "paths".
- # their ip is always a template, with 'X' representing the path index.
- users = {
- mel = {
- key = "vnZoHXapCLLUhZ8A8R5W0iJ8LpWVLve29z41kkoT0BU=";
- ip = "10.123.X.101";
- };
-
- andrei = {
- key = "qqU4uYImLfUohIwl4KBshPtTINFcs0JVALjbmwpfxRg=";
- ip = "10.123.X.102";
- };
-
- sergo = {
- key = "qbZGMNIDZFCJC6SHtlyNIlIdGWHELceXClJCcagrj2Y=";
- ip = "10.123.X.103";
- };
-
- fedor = {
- key = "tEO9r8+jTpu8TBRmZ+/v087IgD/QfmofLUKs249i/F0=";
- ip = "10.123.X.104";
- };
- };
+ inherit (import ../../assets/vpn.nix) users;
# we use a website as a "mask" for vless/reality, which will tell our peers
# to pretend as if they're a user and a well-known website communicating with
# each other, even though they know that the keys don't actually match up,
# it's not possible to see that on the outside.
mask = "microsoft.com";
-
- # we don't actually need this to configure the tunnel, but this is
- # the public key of the ingress interface.
- # when creating wireguard vpn configurations for the users, this
- # is the public key of the server peer at `tunnel.rnrd.eu`.
- # the matching private key of the pair is the secret `tunnel/ingress-key`.
- ingress.public = "s5yyPCJiN0uqW0jzKIbYCF7I9TthymiRzpNt466XeWk=";
}
diff --git a/modules/tunnel/ingress.nix b/modules/tunnel/ingress.nix
index 1ea1613..f609f3d 100644
--- a/modules/tunnel/ingress.nix
+++ b/modules/tunnel/ingress.nix
@@ -38,8 +38,8 @@ in
checkReversePath = "loose";
};
- age.secrets.ingress-key = {
- file = ../../secrets/tunnel/ingress-key.age;
+ age.secrets.wg-private-key = {
+ file = ../../secrets/wg-private-key.age;
owner = "systemd-network";
};
@@ -52,7 +52,7 @@ in
Name = ingressName index;
};
wireguardConfig = {
- PrivateKeyFile = config.age.secrets.ingress-key.path;
+ PrivateKeyFile = config.age.secrets.wg-private-key.path;
ListenPort = path.port;
};
wireguardPeers = map (user: {
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 7332beb..c56e2bd 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -42,15 +42,17 @@ in
fourmi
] ++ allAdmins;
- "vpn/ingress-key.age".publicKeys = [
+ "wg-private-key.age".publicKeys = [
truite
+ taureau
+ taupe
] ++ allAdmins;
- "vpn/egress-key-taureau.age".publicKeys = [
+ "tunnel/egress-key-taureau.age".publicKeys = [
taureau
] ++ allAdmins;
- "vpn/egress-key-taupe.age".publicKeys = [
+ "tunnel/egress-key-taupe.age".publicKeys = [
taupe
] ++ allAdmins;
}
diff --git a/secrets/tunnel/ingress-key.age b/secrets/wg-private-key.age
index 2e83ec2..2e83ec2 100644
--- a/secrets/tunnel/ingress-key.age
+++ b/secrets/wg-private-key.age