Home

infra @main - refs - log -
-
https://git.jolheiser.com/infra.git
My NixOS infrastructure
tree log patch
gunpowder: implement more robust mullvad/qbittorrent/tailscale Signed-off-by: jolheiser <git@jolheiser.com>
Signature
-----BEGIN SSH SIGNATURE----- U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgBTEvCQk6VqUAdN2RuH6bj1dNkY oOpbPWj+jw4ua1B1cAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5 AAAAQA9lEbUuh/dgXH1VqJpG1wPl50cvPnNT1u69TcIeW1nVOlsKjYo1kQzPOT277IROqm R1ZKxfx44mn3lk78Wn6Ak= -----END SSH SIGNATURE-----
jolheiser <git@jolheiser.com>
2 weeks ago
4 changed files, 114 additions(+), 6 deletions(-)
gunpowder/default.nixgunpowder/services/mullvad.nixgunpowder/services/qbittorrent.nixsecrets/mullvad.agesecrets/secrets.nix
M gunpowder/default.nix -> gunpowder/default.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
diff --git a/gunpowder/default.nix b/gunpowder/default.nix
index 00dd9604e462031a376fdea59aeff4336fa25b3c..402101e30f85f0cb5023e3fee179d37264844d85 100644
--- a/gunpowder/default.nix
+++ b/gunpowder/default.nix
@@ -47,10 +47,6 @@       desktopManager.xfce.enable = true;
     };
     openssh.enable = true;
     tailscale.enable = true;
-    mullvad-vpn = {
-      enable = true;
-      package = pkgs.mullvad-vpn;
-    };
     resolved.enable = true;
   };
 
@@ -80,8 +76,6 @@       randomizedDelaySec = "15m";
     };
     optimise.automatic = true;
   };
-
-  environment.systemPackages = with pkgs; [ qbittorrent ];
 
   system.stateVersion = "22.11";
 }
I gunpowder/services/mullvad.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
diff --git a/gunpowder/services/mullvad.nix b/gunpowder/services/mullvad.nix
new file mode 100644
index 0000000000000000000000000000000000000000..ec0f9e85cf5623e25890c692a6b8365de8899c25
--- /dev/null
+++ b/gunpowder/services/mullvad.nix
@@ -0,0 +1,71 @@
+{
+  lib,
+  pkgs,
+  config,
+  ...
+}:
+let
+  enable = true;
+
+  mullvadMark = "0x6d6f6c65";
+
+  bypassRuleset = pkgs.writeText "mullvad-tailscale-bypass.nft" ''
+    table inet mullvad_tailscale_bypass {
+      chain output {
+        type route hook output priority 0; policy accept;
+        ip daddr 100.64.0.0/10 meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+        ip6 daddr fd7a:115c:a1e0::/48 meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+        oifname "tailscale0" meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+      }
+      chain input {
+        type filter hook input priority -100; policy accept;
+        ip saddr 100.64.0.0/10 meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+        ip6 saddr fd7a:115c:a1e0::/48 meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+        iifname "tailscale0" meta mark set ${mullvadMark} ct mark set ${mullvadMark}
+      }
+    }
+  '';
+in
+lib.mkIf enable {
+  age.secrets.mullvad.file = ../../secrets/mullvad.age;
+
+  services.mullvad-vpn = {
+    enable = true;
+    package = pkgs.mullvad-vpn;
+  };
+
+  systemd.services.mullvad-tailscale-bypass = {
+    description = "Exempt tailnet traffic from the Mullvad tunnel and kill switch";
+    wantedBy = [ "multi-user.target" ];
+    after = [ "network-pre.target" ];
+    before = [ "mullvad-daemon.service" ];
+    serviceConfig = {
+      Type = "oneshot";
+      RemainAfterExit = true;
+      ExecStart = pkgs.writeShellScript "mullvad-tailscale-bypass-start" ''
+        ${pkgs.nftables}/bin/nft delete table inet mullvad_tailscale_bypass 2>/dev/null || true
+        ${pkgs.nftables}/bin/nft -f ${bypassRuleset}
+      '';
+    };
+  };
+
+  systemd.services.mullvad-provision = {
+    description = "Log in to Mullvad and lock networking to the tunnel";
+    wantedBy = [ "multi-user.target" ];
+    after = [ "mullvad-daemon.service" ];
+    requires = [ "mullvad-daemon.service" ];
+    path = [ config.services.mullvad-vpn.package ];
+    serviceConfig = {
+      Type = "oneshot";
+      RemainAfterExit = true;
+    };
+    script = ''
+      until mullvad status >/dev/null 2>&1; do sleep 1; done
+      mullvad account login "$(cat ${config.age.secrets.mullvad.path})"
+      mullvad lan set block
+      mullvad lockdown-mode set on
+      mullvad auto-connect set on
+      mullvad connect
+    '';
+  };
+}
I gunpowder/services/qbittorrent.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
diff --git a/gunpowder/services/qbittorrent.nix b/gunpowder/services/qbittorrent.nix
new file mode 100644
index 0000000000000000000000000000000000000000..47570926de3738ff3ca9612e0e04036622afbab6
--- /dev/null
+++ b/gunpowder/services/qbittorrent.nix
@@ -0,0 +1,42 @@
+{ lib, pkgs, ... }:
+let
+  enable = true;
+
+  killSwitchRuleset = pkgs.writeText "qbittorrent-mullvad-only.nft" ''
+    table inet qbittorrent_mullvad_only {
+      chain output {
+        type filter hook output priority -50; policy accept;
+        meta skuid "qbittorrent" oifname != "wg-mullvad" oifname != "lo" drop
+      }
+    }
+  '';
+in
+lib.mkIf enable {
+  services = {
+    qbittorrent = {
+      enable = true;
+      openFirewall = true;
+    };
+    tailproxy.qbittorrent = {
+      enable = true;
+      hostname = "qbittorrent";
+      port = 8080;
+      authKey = "tskey-auth-kV7j5FHHHh11CNTRL-wPSNdNQqza8kXxzpnZXha8B7JCvdwCAy"; # One-time key
+    };
+  };
+
+  systemd.services.qbittorrent-mullvad-only = {
+    description = "Restrict qBittorrent's egress to the Mullvad tunnel only";
+    wantedBy = [ "multi-user.target" ];
+    after = [ "network-pre.target" ];
+    before = [ "qbittorrent.service" ];
+    serviceConfig = {
+      Type = "oneshot";
+      RemainAfterExit = true;
+      ExecStart = pkgs.writeShellScript "qbittorrent-mullvad-only-start" ''
+        ${pkgs.nftables}/bin/nft delete table inet qbittorrent_mullvad_only 2>/dev/null || true
+        ${pkgs.nftables}/bin/nft -f ${killSwitchRuleset}
+      '';
+    };
+  };
+}
I secrets/mullvad.age
1
2
3
4
diff --git a/secrets/mullvad.age b/secrets/mullvad.age
new file mode 100644
index 0000000000000000000000000000000000000000..0ba002c8108c920fc79e250873a2ea897028a7c5
Binary files /dev/null and b/secrets/mullvad.age differ
M secrets/secrets.nix -> secrets/secrets.nix
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index e2c6fa5f0b352eac9528667762bd4e76a390663f..ecaf2b6b19054bbb983353d4fb7fbe1fa808b0dc 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -46,4 +46,5 @@   "cifs.age".publicKeys = jasmineKeys;
   "beanboy.age".publicKeys = peachKeys;
   "oa2p.age".publicKeys = dragonwellKeys;
   "woodpecker.age".publicKeys = dragonwellKeys;
+  "mullvad.age".publicKeys = gunpowderKeys;
 }