at 23.11-beta 7.2 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 USER bitwardenuser WITH PASSWORD '${dbPassword}'; 58 CREATE DATABASE bitwarden WITH OWNER bitwardenuser; 59 ''; 60 }; 61 62 services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden"; 63 64 systemd.services.vaultwarden.after = [ "postgresql.service" ]; 65 }; 66 67 sqlite = { }; 68 }; 69 in 70 mkMerge [ 71 backendConfig.${backend} 72 { 73 services.vaultwarden = { 74 enable = true; 75 dbBackend = backend; 76 config = { 77 rocketAddress = "0.0.0.0"; 78 rocketPort = 80; 79 }; 80 }; 81 82 networking.firewall.allowedTCPPorts = [ 80 ]; 83 84 environment.systemPackages = 85 let 86 testRunner = pkgs.writers.writePython3Bin "test-runner" 87 { 88 libraries = [ pkgs.python3Packages.selenium ]; 89 flakeIgnore = [ 90 "E501" 91 ]; 92 } '' 93 94 from selenium.webdriver.common.by import By 95 from selenium.webdriver import Firefox 96 from selenium.webdriver.firefox.options import Options 97 from selenium.webdriver.support.ui import WebDriverWait 98 from selenium.webdriver.support import expected_conditions as EC 99 100 options = Options() 101 options.add_argument('--headless') 102 driver = Firefox(options=options) 103 104 driver.implicitly_wait(20) 105 driver.get('http://localhost/#/register') 106 107 wait = WebDriverWait(driver, 10) 108 109 wait.until(EC.title_contains("Create account")) 110 111 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_email').send_keys( 112 '${userEmail}' 113 ) 114 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_name').send_keys( 115 'A Cat' 116 ) 117 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_master-password').send_keys( 118 '${userPassword}' 119 ) 120 driver.find_element(By.CSS_SELECTOR, 'input#register-form_input_confirm-master-password').send_keys( 121 '${userPassword}' 122 ) 123 if driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').is_selected(): 124 driver.find_element(By.CSS_SELECTOR, 'input#checkForBreaches').click() 125 126 driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click() 127 128 wait.until_not(EC.title_contains("Create account")) 129 130 driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click() 131 132 driver.find_element(By.CSS_SELECTOR, 'input#login_input_master-password').send_keys( 133 '${userPassword}' 134 ) 135 driver.find_element(By.XPATH, "//button[contains(., 'Log in')]").click() 136 137 wait.until(EC.title_contains("Vaults")) 138 139 driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click() 140 141 driver.find_element(By.CSS_SELECTOR, 'input#name').send_keys( 142 'secrets' 143 ) 144 driver.find_element(By.CSS_SELECTOR, 'input#loginPassword').send_keys( 145 '${storedPassword}' 146 ) 147 148 driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click() 149 ''; 150 in 151 [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ]; 152 153 } 154 ]; 155 156 client = { pkgs, ... }: 157 { 158 environment.systemPackages = [ pkgs.bitwarden-cli ]; 159 }; 160 }; 161 162 testScript = '' 163 start_all() 164 server.wait_for_unit("vaultwarden.service") 165 server.wait_for_open_port(80) 166 167 with subtest("configure the cli"): 168 client.succeed("bw --nointeraction config server http://server") 169 170 with subtest("can't login to nonexistent account"): 171 client.fail( 172 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 173 ) 174 175 with subtest("use the web interface to sign up, log in, and save a password"): 176 server.succeed("PYTHONUNBUFFERED=1 systemd-cat -t test-runner test-runner") 177 178 with subtest("log in with the cli"): 179 key = client.succeed( 180 "bw --nointeraction --raw login ${userEmail} ${userPassword}" 181 ).strip() 182 183 with subtest("sync with the cli"): 184 client.succeed(f"bw --nointeraction --raw --session {key} sync -f") 185 186 with subtest("get the password with the cli"): 187 password = client.succeed( 188 f"bw --nointeraction --raw --session {key} list items | ${pkgs.jq}/bin/jq -r .[].login.password" 189 ) 190 assert password.strip() == "${storedPassword}" 191 ''; 192 }; 193in 194builtins.listToAttrs ( 195 map 196 (backend: { name = backend; value = makeVaultwardenTest backend; }) 197 backends 198)