From d952db86c9f57d50af70e2b15cc9fb2910aedca2 Mon Sep 17 00:00:00 2001 From: h7x4 Date: Thu, 27 Jul 2023 21:13:55 +0200 Subject: nixos/vaultwarden: add test for backup script, fix flaky sqlite test --- nixos/tests/all-tests.nix | 2 +- nixos/tests/vaultwarden.nix | 263 +++++++++++++++++++++++--------------------- 2 files changed, 138 insertions(+), 127 deletions(-) (limited to 'nixos/tests') diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index aa7e007e4fddb..f141fd29133b0 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -1013,7 +1013,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/vaultwarden.nix b/nixos/tests/vaultwarden.nix index 28ff170e36107..5b1d55f406b5f 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/#/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,113 +109,47 @@ 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 = 80; + }; + }; + + networking.firewall.allowedTCPPorts = [ 80 ]; + + 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) @@ -184,15 +174,36 @@ 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}" ''; - }; + }); 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(80) + + 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" ]') + ''; + }; +} -- cgit 1.4.1