at 23.11-pre 7.3 kB view raw
1{ system ? builtins.currentSystem 2, config ? { } 3, pkgs ? import ../.. { inherit system config; } 4}: 5 6# These tests will: 7# * Set up a vaultwarden server 8# * Have Firefox use the web vault to create an account, log in, and save a password to the valut 9# * Have the bw cli log in and read that password from the vault 10# 11# Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured) 12# 13# The same tests should work without modification on the official bitwarden server, if we ever package that. 14 15with import ../lib/testing-python.nix { inherit system pkgs; }; 16with pkgs.lib; 17let 18 backends = [ "sqlite" "mysql" "postgresql" ]; 19 20 dbPassword = "please_dont_hack"; 21 22 userEmail = "meow@example.com"; 23 userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page 24 25 storedPassword = "seeeecret"; 26 27 makeVaultwardenTest = backend: makeTest { 28 name = "vaultwarden-${backend}"; 29 meta = { 30 maintainers = with pkgs.lib.maintainers; [ jjjollyjim ]; 31 }; 32 33 nodes = { 34 server = { pkgs, ... }: 35 let backendConfig = { 36 mysql = { 37 services.mysql = { 38 enable = true; 39 initialScript = pkgs.writeText "mysql-init.sql" '' 40 CREATE DATABASE bitwarden; 41 CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}'; 42 GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost'; 43 FLUSH PRIVILEGES; 44 ''; 45 package = pkgs.mariadb; 46 }; 47 48 services.vaultwarden.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden"; 49 50 systemd.services.vaultwarden.after = [ "mysql.service" ]; 51 }; 52 53 postgresql = { 54 services.postgresql = { 55 enable = true; 56 initialScript = pkgs.writeText "postgresql-init.sql" '' 57 CREATE DATABASE bitwarden; 58 CREATE USER bitwardenuser WITH PASSWORD '${dbPassword}'; 59 GRANT ALL PRIVILEGES ON DATABASE bitwarden TO bitwardenuser; 60 ''; 61 }; 62 63 services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden"; 64 65 systemd.services.vaultwarden.after = [ "postgresql.service" ]; 66 }; 67 68 sqlite = { }; 69 }; 70 in 71 mkMerge [ 72 backendConfig.${backend} 73 { 74 services.vaultwarden = { 75 enable = true; 76 dbBackend = backend; 77 config = { 78 rocketAddress = "0.0.0.0"; 79 rocketPort = 80; 80 }; 81 }; 82 83 networking.firewall.allowedTCPPorts = [ 80 ]; 84 85 environment.systemPackages = 86 let 87 testRunner = pkgs.writers.writePython3Bin "test-runner" 88 { 89 libraries = [ pkgs.python3Packages.selenium ]; 90 flakeIgnore = [ 91 "E501" 92 ]; 93 } '' 94 95 from selenium.webdriver.common.by import By 96 from selenium.webdriver import Firefox 97 from selenium.webdriver.firefox.options import Options 98 from selenium.webdriver.support.ui import WebDriverWait 99 from selenium.webdriver.support import expected_conditions as EC 100 101 options = Options() 102 options.add_argument('--headless') 103 driver = Firefox(options=options) 104 105 driver.implicitly_wait(20) 106 driver.get('http://localhost/#/register') 107 108 wait = WebDriverWait(driver, 10) 109 110 wait.until(EC.title_contains("Create account")) 111 112 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys( 113 '${userEmail}' 114 ) 115 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys( 116 'A Cat' 117 ) 118 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys( 119 '${userPassword}' 120 ) 121 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys( 122 '${userPassword}' 123 ) 124 if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected(): 125 driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click() 126 127 driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click() 128 129 wait.until_not(EC.title_contains("Create account")) 130 131 driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click() 132 133 driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys( 134 '${userPassword}' 135 ) 136 driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click() 137 138 wait.until(EC.title_contains("Vaults")) 139 140 driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click() 141 142 driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys( 143 'secrets' 144 ) 145 driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys( 146 '${storedPassword}' 147 ) 148 149 driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click() 150 ''; 151 in 152 [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ]; 153 154 } 155 ]; 156 157 client = { pkgs, ... }: 158 { 159 environment.systemPackages = [ pkgs.bitwarden-cli ]; 160 }; 161 }; 162 163 testScript = '' 164 start_all() 165 server.wait_for_unit("vaultwarden.service") 166 server.wait_for_open_port(80) 167 168 with subtest("configure the cli"): 169 client.succeed("bw --nointeraction config server http://server") 170 171 with subtest("can't login to nonexistent account"): 172 client.fail( 173 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 174 ) 175 176 with subtest("use the web interface to sign up, log in, and save a password"): 177 server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner") 178 179 with subtest("log in with the cli"): 180 key = client.succeed( 181 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 182 ).strip() 183 184 with subtest("sync with the cli"): 185 client.succeed(f"bw --nointeraction --raw --session {key} sync -f") 186 187 with subtest("get the password with the cli"): 188 password = client.succeed( 189 f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password" 190 ) 191 assert password.strip() == "${storedPassword}" 192 ''; 193 }; 194in 195builtins.listToAttrs ( 196 map 197 (backend: { name = backend; value = makeVaultwardenTest backend; }) 198 backends 199)