at 25.11-pre 9.1 kB view raw
1# These tests will: 2# * Set up a vaultwarden server 3# * Have Firefox use the web vault to create an account, log in, and save a password to the vault 4# * Have the bw cli log in and read that password from the vault 5# 6# Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured) 7# 8# The same tests should work without modification on the official bitwarden server, if we ever package that. 9 10let 11 makeVaultwardenTest = 12 name: 13 { 14 backend ? name, 15 withClient ? true, 16 testScript ? null, 17 }: 18 import ./make-test-python.nix ( 19 { lib, pkgs, ... }: 20 let 21 dbPassword = "please_dont_hack"; 22 userEmail = "meow@example.com"; 23 userPassword = "also_super_secret_ZJWpBKZi668QGt"; # Must be complex to avoid interstitial warning on the signup page 24 storedPassword = "seeeecret"; 25 26 testRunner = 27 pkgs.writers.writePython3Bin "test-runner" 28 { 29 libraries = [ pkgs.python3Packages.selenium ]; 30 flakeIgnore = [ "E501" ]; 31 } 32 '' 33 34 from selenium.webdriver.common.by import By 35 from selenium.webdriver import Firefox 36 from selenium.webdriver.firefox.options import Options 37 from selenium.webdriver.support.ui import WebDriverWait 38 from selenium.webdriver.support import expected_conditions as EC 39 40 options = Options() 41 options.add_argument('--headless') 42 driver = Firefox(options=options) 43 44 driver.implicitly_wait(20) 45 driver.get('http://localhost:8080/#/register') 46 47 wait = WebDriverWait(driver, 10) 48 49 wait.until(EC.title_contains("Vaultwarden Web")) 50 51 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys( 52 '${userEmail}' 53 ) 54 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys( 55 'A Cat' 56 ) 57 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys( 58 '${userPassword}' 59 ) 60 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys( 61 '${userPassword}' 62 ) 63 if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected(): 64 driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click() 65 66 driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click() 67 68 wait.until_not(EC.title_contains("Create account")) 69 70 driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click() 71 72 driver.find_element(By.XPATH, '//input[@type="password"]').send_keys( 73 '${userPassword}' 74 ) 75 driver.find_element(By.XPATH, "//button[contains(., 'Log in with master password')]").click() 76 77 wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'button#newItemDropdown'))).click() 78 driver.find_element(By.XPATH, "//button[contains(., 'Item')]").click() 79 80 driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys( 81 'secrets' 82 ) 83 driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys( 84 '${storedPassword}' 85 ) 86 87 driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click() 88 ''; 89 in 90 { 91 inherit name; 92 93 meta = { 94 maintainers = with pkgs.lib.maintainers; [ 95 dotlambda 96 SuperSandro2000 97 ]; 98 }; 99 100 nodes = 101 { 102 server = 103 { pkgs, ... }: 104 lib.mkMerge [ 105 { 106 mysql = { 107 services.mysql = { 108 enable = true; 109 initialScript = pkgs.writeText "mysql-init.sql" '' 110 CREATE DATABASE bitwarden; 111 CREATE USER 'bitwardenuser'@'localhost' IDENTIFIED BY '${dbPassword}'; 112 GRANT ALL ON `bitwarden`.* TO 'bitwardenuser'@'localhost'; 113 FLUSH PRIVILEGES; 114 ''; 115 package = pkgs.mariadb; 116 }; 117 118 services.vaultwarden.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden"; 119 120 systemd.services.vaultwarden.after = [ "mysql.service" ]; 121 }; 122 123 postgresql = { 124 services.postgresql = { 125 enable = true; 126 ensureDatabases = [ "vaultwarden" ]; 127 ensureUsers = [ 128 { 129 name = "vaultwarden"; 130 ensureDBOwnership = true; 131 } 132 ]; 133 }; 134 135 services.vaultwarden.config.databaseUrl = "postgresql:///vaultwarden?host=/run/postgresql"; 136 137 systemd.services.vaultwarden.after = [ "postgresql.service" ]; 138 }; 139 140 sqlite = { 141 services.vaultwarden.backupDir = "/srv/backups/vaultwarden"; 142 143 environment.systemPackages = [ pkgs.sqlite ]; 144 }; 145 } 146 .${backend} 147 148 { 149 services.vaultwarden = { 150 enable = true; 151 dbBackend = backend; 152 config = { 153 rocketAddress = "::"; 154 rocketPort = 8080; 155 }; 156 }; 157 158 networking.firewall.allowedTCPPorts = [ 8080 ]; 159 160 environment.systemPackages = [ 161 pkgs.firefox-unwrapped 162 pkgs.geckodriver 163 testRunner 164 ]; 165 } 166 ]; 167 } 168 // lib.optionalAttrs withClient { 169 client = 170 { pkgs, ... }: 171 { 172 environment.systemPackages = [ pkgs.bitwarden-cli ]; 173 }; 174 }; 175 176 testScript = 177 if testScript != null then 178 testScript 179 else 180 '' 181 start_all() 182 server.wait_for_unit("vaultwarden.service") 183 server.wait_for_open_port(8080) 184 185 with subtest("configure the cli"): 186 client.succeed("bw --nointeraction config server http://server:8080") 187 188 with subtest("can't login to nonexistent account"): 189 client.fail( 190 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 191 ) 192 193 with subtest("use the web interface to sign up, log in, and save a password"): 194 server.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner") 195 196 with subtest("log in with the cli"): 197 key = client.succeed( 198 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 199 ).strip() 200 201 with subtest("sync with the cli"): 202 client.succeed(f"bw --nointeraction --raw --session {key} sync -f") 203 204 with subtest("get the password with the cli"): 205 password = client.wait_until_succeeds( 206 f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password", 207 timeout=60 208 ) 209 assert password.strip() == "${storedPassword}" 210 211 with subtest("Check systemd unit hardening"): 212 server.log(server.succeed("systemd-analyze security vaultwarden.service | grep -v ")) 213 ''; 214 } 215 ); 216in 217builtins.mapAttrs (k: v: makeVaultwardenTest k v) { 218 mysql = { }; 219 postgresql = { }; 220 sqlite = { }; 221 sqlite-backup = { 222 backend = "sqlite"; 223 withClient = false; 224 225 testScript = '' 226 start_all() 227 server.wait_for_unit("vaultwarden.service") 228 server.wait_for_open_port(8080) 229 230 with subtest("Set up vaultwarden"): 231 server.succeed("PYTHONUNBUFFERED=1 test-runner | systemd-cat -t test-runner") 232 233 with subtest("Run the backup script"): 234 server.start_job("backup-vaultwarden.service") 235 236 with subtest("Check that backup exists"): 237 server.succeed('[ -d "/srv/backups/vaultwarden" ]') 238 server.succeed('[ -f "/srv/backups/vaultwarden/db.sqlite3" ]') 239 server.succeed('[ -d "/srv/backups/vaultwarden/attachments" ]') 240 server.succeed('[ -f "/srv/backups/vaultwarden/rsa_key.pem" ]') 241 # Ensure only the db backed up with the backup command exists and not the other db files. 242 server.succeed('[ ! -f "/srv/backups/vaultwarden/db.sqlite3-shm" ]') 243 ''; 244 }; 245}