diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2411.section.md | 10 | ||||
-rw-r--r-- | nixos/lib/test-driver/default.nix | 13 | ||||
-rw-r--r-- | nixos/modules/programs/ydotool.nix | 25 | ||||
-rw-r--r-- | nixos/modules/services/audio/navidrome.nix | 2 | ||||
-rw-r--r-- | nixos/modules/services/misc/jellyfin.nix | 2 | ||||
-rw-r--r-- | nixos/modules/services/networking/aria2.nix | 7 | ||||
-rw-r--r-- | nixos/modules/services/security/vaultwarden/default.nix | 47 | ||||
-rw-r--r-- | nixos/modules/services/ttys/getty.nix | 3 | ||||
-rw-r--r-- | nixos/modules/services/x11/window-managers/qtile.nix | 39 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 2 | ||||
-rw-r--r-- | nixos/tests/firefly-iii.nix | 4 | ||||
-rw-r--r-- | nixos/tests/qtile.nix | 2 | ||||
-rw-r--r-- | nixos/tests/vaultwarden.nix | 271 | ||||
-rw-r--r-- | nixos/tests/ydotool.nix | 257 |
14 files changed, 405 insertions, 279 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..889d399749323 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -19,6 +19,8 @@ ## Backward Incompatibilities {#sec-release-24.11-incompatibilities} +- `androidenv.androidPkgs_9_0` has been removed, and replaced with `androidenv.androidPkgs` for a more complete Android SDK including support for Android 9 and later. + - `nginx` package no longer includes `gd` and `geoip` dependencies. For enabling it, override `nginx` package with the optionals `withImageFilter` and `withGeoIP`. - `openssh` and `openssh_hpn` are now compiled without Kerberos 5 / GSSAPI support in an effort to reduce the attack surface of the components for the majority of users. Users needing this support can @@ -35,6 +37,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. @@ -67,6 +73,10 @@ services.portunus.ldap.package = pkgs.openldap.override { libxcrypt = pkgs.libxcrypt-legacy; }; ``` +- The `tracy` package no longer works on X11, since it's moved to Wayland + support, which is the intended default behavior by Tracy maintainers. + X11 users have to switch to the new package `tracy-x11`. + ## Other Notable Changes {#sec-release-24.11-notable-changes} <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> diff --git a/nixos/lib/test-driver/default.nix b/nixos/lib/test-driver/default.nix index 7a88694b3167e..26652db6016e6 100644 --- a/nixos/lib/test-driver/default.nix +++ b/nixos/lib/test-driver/default.nix @@ -13,11 +13,20 @@ , extraPythonPackages ? (_ : []) , nixosTests }: - +let + fs = lib.fileset; +in python3Packages.buildPythonApplication { pname = "nixos-test-driver"; version = "1.1"; - src = ./.; + src = fs.toSource { + root = ./.; + fileset = fs.unions [ + ./pyproject.toml + ./test_driver + ./extract-docstrings.py + ]; + }; pyproject = true; propagatedBuildInputs = [ diff --git a/nixos/modules/programs/ydotool.nix b/nixos/modules/programs/ydotool.nix index f639e9283de42..643a5d369f3fc 100644 --- a/nixos/modules/programs/ydotool.nix +++ b/nixos/modules/programs/ydotool.nix @@ -14,23 +14,32 @@ in options.programs.ydotool = { enable = lib.mkEnableOption '' - ydotoold system service and install ydotool. - Add yourself to the 'ydotool' group to be able to use it. + ydotoold system service and {command}`ydotool` for members of + {option}`programs.ydotool.group`. ''; + group = lib.mkOption { + type = lib.types.str; + default = "ydotool"; + description = '' + Group which users must be in to use {command}`ydotool`. + ''; + }; }; - config = lib.mkIf cfg.enable { - users.groups.ydotool = { }; + config = let + runtimeDirectory = "ydotoold"; + in lib.mkIf cfg.enable { + users.groups."${config.programs.ydotool.group}" = { }; systemd.services.ydotoold = { description = "ydotoold - backend for ydotool"; wantedBy = [ "multi-user.target" ]; partOf = [ "multi-user.target" ]; serviceConfig = { - Group = "ydotool"; - RuntimeDirectory = "ydotoold"; + Group = config.programs.ydotool.group; + RuntimeDirectory = runtimeDirectory; RuntimeDirectoryMode = "0750"; - ExecStart = "${lib.getExe' pkgs.ydotool "ydotoold"} --socket-path=/run/ydotoold/socket --socket-perm=0660"; + ExecStart = "${lib.getExe' pkgs.ydotool "ydotoold"} --socket-path=${config.environment.variables.YDOTOOL_SOCKET} --socket-perm=0660"; # hardening @@ -76,7 +85,7 @@ in }; environment.variables = { - YDOTOOL_SOCKET = "/run/ydotoold/socket"; + YDOTOOL_SOCKET = "/run/${runtimeDirectory}/socket"; }; environment.systemPackages = with pkgs; [ ydotool ]; }; diff --git a/nixos/modules/services/audio/navidrome.nix b/nixos/modules/services/audio/navidrome.nix index a9db9228827a2..06d2d174a4df3 100644 --- a/nixos/modules/services/audio/navidrome.nix +++ b/nixos/modules/services/audio/navidrome.nix @@ -157,5 +157,5 @@ in networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [ cfg.settings.Port ]; }; - meta.maintainers = with maintainers; [ nu-nu-ko ]; + meta.maintainers = with maintainers; [ fsnkty ]; } diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix index a1d3910bd93b0..a006090878422 100644 --- a/nixos/modules/services/misc/jellyfin.nix +++ b/nixos/modules/services/misc/jellyfin.nix @@ -160,5 +160,5 @@ in }; - meta.maintainers = with maintainers; [ minijackson nu-nu-ko ]; + meta.maintainers = with maintainers; [ minijackson fsnkty ]; } diff --git a/nixos/modules/services/networking/aria2.nix b/nixos/modules/services/networking/aria2.nix index dd4823911f2b3..f0d5c5c8a21e3 100644 --- a/nixos/modules/services/networking/aria2.nix +++ b/nixos/modules/services/networking/aria2.nix @@ -7,12 +7,6 @@ let defaultRpcListenPort = 6800; defaultDir = "${homeDir}/Downloads"; - rangesToStringList = map (x: - if x.from == x.to - then builtins.toString x.from - else builtins.toString x.from + "-" + builtins.toString x.to - ); - portRangesToString = ranges: lib.concatStringsSep "," (map (x: if x.from == x.to @@ -77,6 +71,7 @@ in [0]: https://aria2.github.io/manual/en/html/aria2c.html#synopsis ''; + default = { }; type = lib.types.submodule { freeformType = with lib.types; attrsOf (oneOf [ bool int float singleLineStr ]); options = { diff --git a/nixos/modules/services/security/vaultwarden/default.nix b/nixos/modules/services/security/vaultwarden/default.nix index 33957be437b30..41f7de5d80fab 100644 --- a/nixos/modules/services/security/vaultwarden/default.nix +++ b/nixos/modules/services/security/vaultwarden/default.nix @@ -5,6 +5,8 @@ let user = config.users.users.vaultwarden.name; group = config.users.groups.vaultwarden.name; + StateDirectory = if lib.versionOlder config.system.stateVersion "24.11" then "bitwarden_rs" else "vaultwarden"; + # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER). nameToEnvVar = name: let @@ -23,7 +25,7 @@ let configEnv = lib.concatMapAttrs (name: value: lib.optionalAttrs (value != null) { ${nameToEnvVar name} = if lib.isBool value then lib.boolToString value else toString value; }) cfg.config; - in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") { + in { DATA_FOLDER = "/var/lib/${StateDirectory}"; } // lib.optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") { WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault"; } // configEnv; @@ -176,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"; - StateDirectory = "bitwarden_rs"; + 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" ]; }; @@ -193,7 +224,7 @@ in { systemd.services.backup-vaultwarden = lib.mkIf (cfg.backupDir != null) { description = "Backup vaultwarden"; environment = { - DATA_FOLDER = "/var/lib/bitwarden_rs"; + DATA_FOLDER = "/var/lib/${StateDirectory}"; BACKUP_FOLDER = cfg.backupDir; }; path = with pkgs; [ sqlite ]; diff --git a/nixos/modules/services/ttys/getty.nix b/nixos/modules/services/ttys/getty.nix index 011016dd5fd14..e88bb4628635e 100644 --- a/nixos/modules/services/ttys/getty.nix +++ b/nixos/modules/services/ttys/getty.nix @@ -101,7 +101,7 @@ in config = { # Note: this is set here rather than up there so that changing # nixos.label would not rebuild manual pages - services.getty.greetingLine = mkDefault ''<<< Welcome to NixOS ${config.system.nixos.label} (\m) - \l >>>''; + services.getty.greetingLine = mkDefault ''<<< Welcome to ${config.system.nixos.distroName} ${config.system.nixos.label} (\m) - \l >>>''; services.getty.helpLine = mkIf (config.documentation.nixos.enable && config.documentation.doc.enable) "\nRun 'nixos-help' for the NixOS manual."; systemd.services."getty@" = @@ -158,4 +158,5 @@ in }; + meta.maintainers = with maintainers; [ RossComputerGuy ]; } diff --git a/nixos/modules/services/x11/window-managers/qtile.nix b/nixos/modules/services/x11/window-managers/qtile.nix index 700ead8366008..4603ca3fb50f0 100644 --- a/nixos/modules/services/x11/window-managers/qtile.nix +++ b/nixos/modules/services/x11/window-managers/qtile.nix @@ -7,6 +7,10 @@ let in { + imports = [ + (mkRemovedOptionModule [ "services" "xserver" "windowManager" "qtile" "backend" ] "The qtile package now provides separate display sessions for both X11 and Wayland.") + ]; + options.services.xserver.windowManager.qtile = { enable = mkEnableOption "qtile"; @@ -22,14 +26,6 @@ in ''; }; - backend = mkOption { - type = types.enum [ "x11" "wayland" ]; - default = "x11"; - description = '' - Backend to use in qtile: `x11` or `wayland`. - ''; - }; - extraPackages = mkOption { type = types.functionTo (types.listOf types.package); default = _: []; @@ -57,25 +53,14 @@ in }; config = mkIf cfg.enable { - services.xserver.windowManager.qtile.finalPackage = pkgs.python3.withPackages (p: - [ (cfg.package.unwrapped or cfg.package) ] ++ (cfg.extraPackages p) - ); - - services.xserver.windowManager.session = [{ - name = "qtile"; - start = '' - ${cfg.finalPackage}/bin/qtile start -b ${cfg.backend} \ - ${optionalString (cfg.configFile != null) - "--config \"${cfg.configFile}\""} & - waitPID=$! - ''; - }]; + services = { + xserver.windowManager.qtile.finalPackage = pkgs.python3.pkgs.qtile.override { extraPackages = cfg.extraPackages pkgs.python3.pkgs; }; + displayManager.sessionPackages = [ cfg.finalPackage ]; + }; - environment.systemPackages = [ - # pkgs.qtile is currently a buildenv of qtile and its dependencies. - # For userland commands, we want the underlying package so that - # packages such as python don't bleed into userland and overwrite intended behavior. - (cfg.package.unwrapped or cfg.package) - ]; + environment = { + etc."xdg/qtile/config.py" = mkIf (cfg.configFile != null) { source = cfg.configFile; }; + systemPackages = [ cfg.finalPackage ]; + }; }; } diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index a9b6881aab0f8..746b29fd27258 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1019,7 +1019,7 @@ in { vault-agent = handleTest ./vault-agent.nix {}; vault-dev = handleTest ./vault-dev.nix {}; vault-postgresql = handleTest ./vault-postgresql.nix {}; - vaultwarden = handleTest ./vaultwarden.nix {}; + vaultwarden = discoverTests (import ./vaultwarden.nix); vector = handleTest ./vector {}; vengi-tools = handleTest ./vengi-tools.nix {}; victoriametrics = handleTest ./victoriametrics.nix {}; diff --git a/nixos/tests/firefly-iii.nix b/nixos/tests/firefly-iii.nix index 2373ba8360264..f8e4ca4bfe2b4 100644 --- a/nixos/tests/firefly-iii.nix +++ b/nixos/tests/firefly-iii.nix @@ -39,12 +39,13 @@ in DB_DATABASE = "firefly"; DB_USERNAME = "firefly"; DB_PASSWORD_FILE = "/etc/postgres-pass"; + PGSQL_SCHEMA = "firefly"; }; }; services.postgresql = { enable = true; - package = pkgs.postgresql_15; + package = pkgs.postgresql_16; authentication = '' local all postgres peer local firefly firefly password @@ -52,6 +53,7 @@ in initialScript = pkgs.writeText "firefly-init.sql" '' CREATE USER "firefly" WITH LOGIN PASSWORD '${db-pass}'; CREATE DATABASE "firefly" WITH OWNER "firefly"; + \c firefly CREATE SCHEMA AUTHORIZATION firefly; ''; }; diff --git a/nixos/tests/qtile.nix b/nixos/tests/qtile.nix index b4d8f9d421144..96afaa342c524 100644 --- a/nixos/tests/qtile.nix +++ b/nixos/tests/qtile.nix @@ -10,7 +10,7 @@ import ./make-test-python.nix ({ lib, ...} : { test-support.displayManager.auto.user = "alice"; services.xserver.windowManager.qtile.enable = true; - services.displayManager.defaultSession = lib.mkForce "none+qtile"; + services.displayManager.defaultSession = lib.mkForce "qtile"; environment.systemPackages = [ pkgs.kitty ]; }; diff --git a/nixos/tests/vaultwarden.nix b/nixos/tests/vaultwarden.nix index 28ff170e36107..baefa67dbf535 100644 --- a/nixos/tests/vaultwarden.nix +++ b/nixos/tests/vaultwarden.nix @@ -1,38 +1,94 @@ -{ system ? builtins.currentSystem -, config ? { } -, pkgs ? import ../.. { inherit system config; } -}: - # These tests will: # * Set up a vaultwarden server -# * Have Firefox use the web vault to create an account, log in, and save a password to the valut +# * Have Firefox use the web vault to create an account, log in, and save a password to the vault # * Have the bw cli log in and read that password from the vault # # Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured) # # The same tests should work without modification on the official bitwarden server, if we ever package that. -with import ../lib/testing-python.nix { inherit system pkgs; }; -with pkgs.lib; let - backends = [ "sqlite" "mysql" "postgresql" ]; - - dbPassword = "please_dont_hack"; - - userEmail = "meow@example.com"; - userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page - - storedPassword = "seeeecret"; + makeVaultwardenTest = name: { + backend ? name, + withClient ? true, + testScript ? null, + }: import ./make-test-python.nix ({ lib, pkgs, ...}: let + dbPassword = "please_dont_hack"; + userEmail = "meow@example.com"; + userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page + storedPassword = "seeeecret"; + + testRunner = pkgs.writers.writePython3Bin "test-runner" { + libraries = [ pkgs.python3Packages.selenium ]; + flakeIgnore = [ "E501" ]; + } '' + + from selenium.webdriver.common.by import By + from selenium.webdriver import Firefox + from selenium.webdriver.firefox.options import Options + from selenium.webdriver.support.ui import WebDriverWait + from selenium.webdriver.support import expected_conditions as EC + + options = Options() + options.add_argument('--headless') + driver = Firefox(options=options) + + driver.implicitly_wait(20) + driver.get('http://localhost:8080/#/register') + + wait = WebDriverWait(driver, 10) + + wait.until(EC.title_contains("Vaultwarden Web")) + + driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys( + '${userEmail}' + ) + driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys( + 'A Cat' + ) + driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys( + '${userPassword}' + ) + driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys( + '${userPassword}' + ) + if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected(): + driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click() + + driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click() + + wait.until_not(EC.title_contains("Create account")) + + driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click() + + driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys( + '${userPassword}' + ) + driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click() + + wait.until(EC.title_contains("Vaults")) + + driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click() + + driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys( + 'secrets' + ) + driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys( + '${storedPassword}' + ) + + driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click() + ''; + in { + inherit name; - makeVaultwardenTest = backend: makeTest { - name = "vaultwarden-${backend}"; meta = { - maintainers = with pkgs.lib.maintainers; [ jjjollyjim ]; + maintainers = with pkgs.lib.maintainers; [ dotlambda SuperSandro2000 ]; }; nodes = { - server = { pkgs, ... }: - let backendConfig = { + server = { pkgs, ... }: lib.mkMerge [ + { mysql = { services.mysql = { enable = true; @@ -53,119 +109,53 @@ let postgresql = { services.postgresql = { enable = true; - initialScript = pkgs.writeText "postgresql-init.sql" '' - CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}'; - CREATE DATABASE bitwarden WITH OWNER bitwardenuser; - ''; + ensureDatabases = [ "vaultwarden" ]; + ensureUsers = [{ + name = "vaultwarden"; + ensureDBOwnership = true; + }]; }; - services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden"; + services.vaultwarden.config.databaseUrl = "postgresql:///vaultwarden?host=/run/postgresql"; systemd.services.vaultwarden.after = [ "postgresql.service" ]; }; - sqlite = { }; - }; - in - mkMerge [ - backendConfig.${backend} - { - services.vaultwarden = { - enable = true; - dbBackend = backend; - config = { - rocketAddress = "0.0.0.0"; - rocketPort = 80; - }; - }; + sqlite = { + services.vaultwarden.backupDir = "/var/lib/vaultwarden/backups"; + + environment.systemPackages = [ pkgs.sqlite ]; + }; + }.${backend} - networking.firewall.allowedTCPPorts = [ 80 ]; - - environment.systemPackages = - let - testRunner = pkgs.writers.writePython3Bin "test-runner" - { - libraries = [ pkgs.python3Packages.selenium ]; - flakeIgnore = [ - "E501" - ]; - } '' - - from selenium.webdriver.common.by import By - from selenium.webdriver import Firefox - from selenium.webdriver.firefox.options import Options - from selenium.webdriver.support.ui import WebDriverWait - from selenium.webdriver.support import expected_conditions as EC - - options = Options() - options.add_argument('--headless') - driver = Firefox(options=options) - - driver.implicitly_wait(20) - driver.get('http://localhost/#/register') - - wait = WebDriverWait(driver, 10) - - wait.until(EC.title_contains("Vaultwarden Web")) - - driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys( - '${userEmail}' - ) - driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys( - 'A Cat' - ) - driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys( - '${userPassword}' - ) - driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys( - '${userPassword}' - ) - if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected(): - driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click() - - driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click() - - wait.until_not(EC.title_contains("Create account")) - - driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click() - - driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys( - '${userPassword}' - ) - driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click() - - wait.until(EC.title_contains("Vaults")) - - driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click() - - driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys( - 'secrets' - ) - driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys( - '${storedPassword}' - ) - - driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click() - ''; - in - [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ]; - - } - ]; - - client = { pkgs, ... }: { - environment.systemPackages = [ pkgs.bitwarden-cli ]; - }; + services.vaultwarden = { + enable = true; + dbBackend = backend; + config = { + rocketAddress = "0.0.0.0"; + rocketPort = 8080; + }; + }; + + networking.firewall.allowedTCPPorts = [ 8080 ]; + + environment.systemPackages = [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ]; + } + ]; + } // lib.optionalAttrs withClient { + client = { pkgs, ... }: { + environment.systemPackages = [ pkgs.bitwarden-cli ]; + }; }; - testScript = '' + 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( @@ -184,15 +174,40 @@ let client.succeed(f"bw --nointeraction --raw --session {key} sync -f") with subtest("get the password with the cli"): - password = client.succeed( - f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password" + password = client.wait_until_succeeds( + f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password", + timeout=60 ) assert password.strip() == "${storedPassword}" + + with subtest("Check systemd unit hardening"): + server.log(server.succeed("systemd-analyze security vaultwarden.service | grep -v ✓")) ''; - }; + }); in -builtins.listToAttrs ( - map - (backend: { name = backend; value = makeVaultwardenTest backend; }) - backends -) +builtins.mapAttrs (k: v: makeVaultwardenTest k v) { + mysql = {}; + postgresql = {}; + sqlite = {}; + sqlite-backup = { + backend = "sqlite"; + withClient = false; + + testScript = '' + start_all() + server.wait_for_unit("vaultwarden.service") + server.wait_for_open_port(8080) + + with subtest("Set up vaultwarden"): + server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner") + + with subtest("Run the backup script"): + server.start_job("backup-vaultwarden.service") + + with subtest("Check that backup exists"): + server.succeed('[ -d "/var/lib/vaultwarden/backups" ]') + server.succeed('[ -f "/var/lib/vaultwarden/backups/db.sqlite3" ]') + server.succeed('[ -d "/var/lib/vaultwarden/backups/attachments" ]') + ''; + }; +} diff --git a/nixos/tests/ydotool.nix b/nixos/tests/ydotool.nix index 818ac6f2d50de..45e3d27adeb49 100644 --- a/nixos/tests/ydotool.nix +++ b/nixos/tests/ydotool.nix @@ -1,115 +1,184 @@ -import ./make-test-python.nix ( - { pkgs, lib, ... }: - let - textInput = "This works."; - inputBoxText = "Enter input"; - inputBox = pkgs.writeShellScript "zenity-input" '' - ${lib.getExe pkgs.gnome.zenity} --entry --text '${inputBoxText}:' > /tmp/output & - ''; - in - { - name = "ydotool"; - - meta = { - maintainers = with lib.maintainers; [ - OPNA2608 - quantenzitrone - ]; - }; +{ + system ? builtins.currentSystem, + config ? { }, + pkgs ? import ../.. { inherit system config; }, + lib ? pkgs.lib, +}: +let + makeTest = import ./make-test-python.nix; + textInput = "This works."; + inputBoxText = "Enter input"; + inputBox = pkgs.writeShellScript "zenity-input" '' + ${lib.getExe pkgs.gnome.zenity} --entry --text '${inputBoxText}:' > /tmp/output & + ''; + asUser = '' + def as_user(cmd: str): + """ + Return a shell command for running a shell command as a specific user. + """ + return f"sudo -u alice -i {cmd}" + ''; +in +{ + headless = makeTest { + name = "headless"; - nodes = { - headless = - { config, ... }: - { - imports = [ ./common/user-account.nix ]; + enableOCR = true; - users.users.alice.extraGroups = [ "ydotool" ]; + nodes.machine = { + imports = [ ./common/user-account.nix ]; - programs.ydotool.enable = true; + users.users.alice.extraGroups = [ "ydotool" ]; - services.getty.autologinUser = "alice"; - }; + programs.ydotool.enable = true; - x11 = - { config, ... }: - { - imports = [ - ./common/user-account.nix - ./common/auto.nix - ./common/x11.nix - ]; + services.getty.autologinUser = "alice"; + }; - users.users.alice.extraGroups = [ "ydotool" ]; + testScript = + asUser + + '' + start_all() - programs.ydotool.enable = true; + machine.wait_for_unit("multi-user.target") + machine.wait_for_text("alice") + machine.succeed(as_user("ydotool type 'echo ${textInput} > /tmp/output'")) # text input + machine.succeed(as_user("ydotool key 28:1 28:0")) # text input + machine.screenshot("headless_input") + machine.wait_for_file("/tmp/output") + machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input + ''; - test-support.displayManager.auto = { - enable = true; - user = "alice"; - }; + meta.maintainers = with lib.maintainers; [ + OPNA2608 + quantenzitrone + ]; + }; - services.xserver.windowManager.dwm.enable = true; - services.displayManager.defaultSession = lib.mkForce "none+dwm"; - }; + x11 = makeTest { + name = "x11"; - wayland = - { config, ... }: - { - imports = [ ./common/user-account.nix ]; + enableOCR = true; - services.cage = { - enable = true; - user = "alice"; - }; + nodes.machine = { + imports = [ + ./common/user-account.nix + ./common/auto.nix + ./common/x11.nix + ]; - programs.ydotool.enable = true; + users.users.alice.extraGroups = [ "ydotool" ]; - services.cage.program = inputBox; - }; + programs.ydotool.enable = true; + + test-support.displayManager.auto = { + enable = true; + user = "alice"; + }; + + services.xserver.windowManager.dwm.enable = true; + services.displayManager.defaultSession = lib.mkForce "none+dwm"; }; + testScript = + asUser + + '' + start_all() + + machine.wait_for_x() + machine.execute(as_user("${inputBox}")) + machine.wait_for_text("${inputBoxText}") + machine.succeed(as_user("ydotool type '${textInput}'")) # text input + machine.screenshot("x11_input") + machine.succeed(as_user("ydotool mousemove -a 400 110")) # mouse input + machine.succeed(as_user("ydotool click 0xC0")) # mouse input + machine.wait_for_file("/tmp/output") + machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input + ''; + + meta.maintainers = with lib.maintainers; [ + OPNA2608 + quantenzitrone + ]; + }; + + wayland = makeTest { + name = "wayland"; + enableOCR = true; - testScript = - { nodes, ... }: - '' - def as_user(cmd: str): - """ - Return a shell command for running a shell command as a specific user. - """ - return f"sudo -u alice -i {cmd}" + nodes.machine = { + imports = [ ./common/user-account.nix ]; + + services.cage = { + enable = true; + user = "alice"; + }; + + programs.ydotool.enable = true; + + services.cage.program = inputBox; + }; + + testScript = '' + start_all() + + machine.wait_for_unit("graphical.target") + machine.wait_for_text("${inputBoxText}") + machine.succeed("ydotool type '${textInput}'") # text input + machine.screenshot("wayland_input") + machine.succeed("ydotool mousemove -a 100 100") # mouse input + machine.succeed("ydotool click 0xC0") # mouse input + machine.wait_for_file("/tmp/output") + machine.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input + ''; + + meta.maintainers = with lib.maintainers; [ + OPNA2608 + quantenzitrone + ]; + }; + + customGroup = + let + name = "customGroup"; + nodeName = "${name}Node"; + insideGroupUsername = "ydotool-user"; + outsideGroupUsername = "other-user"; + groupName = "custom-group"; + in + makeTest { + inherit name; + + nodes."${nodeName}" = { + programs.ydotool = { + enable = true; + group = groupName; + }; + + users.users = { + "${insideGroupUsername}" = { + isNormalUser = true; + extraGroups = [ groupName ]; + }; + "${outsideGroupUsername}".isNormalUser = true; + }; + }; + testScript = '' start_all() - # Headless - headless.wait_for_unit("multi-user.target") - headless.wait_for_text("alice") - headless.succeed(as_user("ydotool type 'echo ${textInput} > /tmp/output'")) # text input - headless.succeed(as_user("ydotool key 28:1 28:0")) # text input - headless.screenshot("headless_input") - headless.wait_for_file("/tmp/output") - headless.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input - - # X11 - x11.wait_for_x() - x11.execute(as_user("${inputBox}")) - x11.wait_for_text("${inputBoxText}") - x11.succeed(as_user("ydotool type '${textInput}'")) # text input - x11.screenshot("x11_input") - x11.succeed(as_user("ydotool mousemove -a 400 110")) # mouse input - x11.succeed(as_user("ydotool click 0xC0")) # mouse input - x11.wait_for_file("/tmp/output") - x11.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input - - # Wayland - wayland.wait_for_unit("graphical.target") - wayland.wait_for_text("${inputBoxText}") - wayland.succeed("ydotool type '${textInput}'") # text input - wayland.screenshot("wayland_input") - wayland.succeed("ydotool mousemove -a 100 100") # mouse input - wayland.succeed("ydotool click 0xC0") # mouse input - wayland.wait_for_file("/tmp/output") - wayland.wait_until_succeeds("grep '${textInput}' /tmp/output") # text input + # Wait for service to start + ${nodeName}.wait_for_unit("multi-user.target") + ${nodeName}.wait_for_unit("ydotoold.service") + + # Verify that user with the configured group can use the service + ${nodeName}.succeed("sudo --login --user=${insideGroupUsername} ydotool type 'Hello, World!'") + + # Verify that user without the configured group can't use the service + ${nodeName}.fail("sudo --login --user=${outsideGroupUsername} ydotool type 'Hello, World!'") ''; - } -) + + meta.maintainers = with lib.maintainers; [ l0b0 ]; + }; +} |