about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMartin Weinelt <hexa@darmstadt.ccc.de>2024-06-11 02:42:39 +0200
committerSandro Jäckel <sandro.jaeckel@gmail.com>2024-06-16 01:33:12 +0200
commitbe53df7236663eda1fc4fed4461813289872745c (patch)
tree77a62ede9858390238d543257f026e832299f80b
parentd8c8faf8c3966456d63794e7d4685def105d36f9 (diff)
nixos/vaultwarden: harden systemd unit
Drops the capability to bind to privileged ports.
-rw-r--r--nixos/doc/manual/release-notes/rl-2411.section.md4
-rw-r--r--nixos/modules/services/security/vaultwarden/default.nix39
-rw-r--r--nixos/tests/vaultwarden.nix15
3 files changed, 47 insertions, 11 deletions
diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md
index 7777df071b182..077712be52e0c 100644
--- a/nixos/doc/manual/release-notes/rl-2411.section.md
+++ b/nixos/doc/manual/release-notes/rl-2411.section.md
@@ -35,6 +35,10 @@
 
 - `services.ddclient.use` has been deprecated: `ddclient` now supports separate IPv4 and IPv6 configuration. Use `services.ddclient.usev4` and `services.ddclient.usev6` instead.
 
+- `vaultwarden` lost the capability to bind to privileged ports. If you rely on
+   this behavior, override the systemd unit to allow `CAP_NET_BIND_SERVICE` in
+   your local configuration.
+
 - The Invoiceplane module now only accepts the structured `settings` option.
   `extraConfig` is now removed.
 
diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix
index 0f2ba4d3f5b7c..41f7de5d80fab 100644
--- a/nixos/modules/services/security/vaultwarden/default.nix
+++ b/nixos/modules/services/security/vaultwarden/default.nix
@@ -178,16 +178,45 @@ in {
         User = user;
         Group = group;
         EnvironmentFile = [ configFile ] ++ lib.optional (cfg.environmentFile != null) cfg.environmentFile;
-        ExecStart = "${vaultwarden}/bin/vaultwarden";
+        ExecStart = lib.getExe vaultwarden;
         LimitNOFILE = "1048576";
-        PrivateTmp = "true";
-        PrivateDevices = "true";
-        ProtectHome = "true";
+        CapabilityBoundingSet = [ "" ];
+        DeviceAllow = [ "" ];
+        DevicePolicy = "closed";
+        LockPersonality = true;
+        MemoryDenyWriteExecute = true;
+        NoNewPrivileges = true;
+        PrivateDevices = true;
+        PrivateTmp = true;
+        PrivateUsers = true;
+        ProcSubset = "pid";
+        ProtectClock = true;
+        ProtectControlGroups = true;
+        ProtectHome = true;
+        ProtectHostname = true;
+        ProtectKernelLogs = true;
+        ProtectKernelModules = true;
+        ProtectKernelTunables = true;
+        ProtectProc = "noaccess";
         ProtectSystem = "strict";
-        AmbientCapabilities = "CAP_NET_BIND_SERVICE";
+        RemoveIPC = true;
+        RestrictAddressFamilies = [
+          "AF_INET"
+          "AF_INET6"
+          "AF_UNIX"
+        ];
+        RestrictNamespaces = true;
+        RestrictRealtime = true;
+        RestrictSUIDSGID = true;
         inherit StateDirectory;
         StateDirectoryMode = "0700";
+        SystemCallArchitectures = "native";
+        SystemCallFilter = [
+          "@system-service"
+          "~@privileged"
+        ];
         Restart = "always";
+        UMask = "0077";
       };
       wantedBy = [ "multi-user.target" ];
     };
diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix
index 3aba3f6845fa7..baefa67dbf535 100644
--- a/nixos/tests/vaultwarden.nix
+++ b/nixos/tests/vaultwarden.nix
@@ -34,7 +34,7 @@ let
       driver = Firefox(options=options)
 
       driver.implicitly_wait(20)
-      driver.get('http://localhost/#/register')
+      driver.get('http://localhost:8080/#/register')
 
       wait = WebDriverWait(driver, 10)
 
@@ -134,11 +134,11 @@ let
             dbBackend = backend;
             config = {
               rocketAddress = "0.0.0.0";
-              rocketPort = 80;
+              rocketPort = 8080;
             };
           };
 
-          networking.firewall.allowedTCPPorts = [ 80 ];
+          networking.firewall.allowedTCPPorts = [ 8080 ];
 
           environment.systemPackages = [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
         }
@@ -152,10 +152,10 @@ let
     testScript = if testScript != null then testScript else ''
       start_all()
       server.wait_for_unit("vaultwarden.service")
-      server.wait_for_open_port(80)
+      server.wait_for_open_port(8080)
 
       with subtest("configure the cli"):
-          client.succeed("bw --nointeraction config server http://server")
+          client.succeed("bw --nointeraction config server http://server:8080")
 
       with subtest("can't login to nonexistent account"):
           client.fail(
@@ -179,6 +179,9 @@ let
               timeout=60
           )
           assert password.strip() == "${storedPassword}"
+
+      with subtest("Check systemd unit hardening"):
+          server.log(server.succeed("systemd-analyze security vaultwarden.service | grep -v ✓"))
     '';
   });
 in
@@ -193,7 +196,7 @@ builtins.mapAttrs (k: v: makeVaultwardenTest k v) {
     testScript = ''
       start_all()
       server.wait_for_unit("vaultwarden.service")
-      server.wait_for_open_port(80)
+      server.wait_for_open_port(8080)
 
       with subtest("Set up vaultwarden"):
           server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner")