Release NixOS 23.11
[NixPkgs.git] / nixos / tests / vaultwarden.nix
blob9d2f0e6ab060efc4385e8d6f88d9993e9e90dcbb
1 { system ? builtins.currentSystem
2 , config ? { }
3 , pkgs ? import ../.. { inherit system config; }
4 }:
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
11 # Note that Firefox must be on the same machine as the server for WebCrypto APIs to be available (or HTTPS must be configured)
13 # The same tests should work without modification on the official bitwarden server, if we ever package that.
15 with import ../lib/testing-python.nix { inherit system pkgs; };
16 with pkgs.lib;
17 let
18   backends = [ "sqlite" "mysql" "postgresql" ];
20   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
25   storedPassword = "seeeecret";
27   makeVaultwardenTest = backend: makeTest {
28     name = "vaultwarden-${backend}";
29     meta = {
30       maintainers = with pkgs.lib.maintainers; [ jjjollyjim ];
31     };
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             };
48             services.vaultwarden.config.databaseUrl = "mysql://bitwardenuser:${dbPassword}@localhost/bitwarden";
50             systemd.services.vaultwarden.after = [ "mysql.service" ];
51           };
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             };
62             services.vaultwarden.config.databaseUrl = "postgresql://bitwardenuser:${dbPassword}@localhost/bitwarden";
64             systemd.services.vaultwarden.after = [ "postgresql.service" ];
65           };
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             };
82             networking.firewall.allowedTCPPorts = [ 80 ];
84             environment.systemPackages =
85               let
86                 testRunner = pkgs.writers.writePython3Bin "test-runner"
87                   {
88                     libraries = [ pkgs.python3Packages.selenium ];
89                     flakeIgnore = [
90                       "E501"
91                     ];
92                   } ''
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
100                   options = Options()
101                   options.add_argument('--headless')
102                   driver = Firefox(options=options)
104                   driver.implicitly_wait(20)
105                   driver.get('http://localhost/#/register')
107                   wait = WebDriverWait(driver, 10)
109                   wait.until(EC.title_contains("Create account"))
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()
126                   driver.find_element(By.XPATH, "//button[contains(., 'Create account')]").click()
128                   wait.until_not(EC.title_contains("Create account"))
130                   driver.find_element(By.XPATH, "//button[contains(., 'Continue')]").click()
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()
137                   wait.until(EC.title_contains("Vaults"))
139                   driver.find_element(By.XPATH, "//button[contains(., 'New item')]").click()
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                   )
148                   driver.find_element(By.XPATH, "//button[contains(., 'Save')]").click()
149                 '';
150               in
151               [ pkgs.firefox-unwrapped pkgs.geckodriver testRunner ];
153           }
154         ];
156       client = { pkgs, ... }:
157         {
158           environment.systemPackages = [ pkgs.bitwarden-cli ];
159         };
160     };
162     testScript = ''
163       start_all()
164       server.wait_for_unit("vaultwarden.service")
165       server.wait_for_open_port(80)
167       with subtest("configure the cli"):
168           client.succeed("bw --nointeraction config server http://server")
170       with subtest("can't login to nonexistent account"):
171           client.fail(
172               "bw --nointeraction --raw login ${userEmail} ${userPassword}"
173           )
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")
178       with subtest("log in with the cli"):
179           key = client.succeed(
180               "bw --nointeraction --raw login ${userEmail} ${userPassword}"
181           ).strip()
183       with subtest("sync with the cli"):
184           client.succeed(f"bw --nointeraction --raw --session {key} sync -f")
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   };
194 builtins.listToAttrs (
195   map
196     (backend: { name = backend; value = makeVaultwardenTest backend; })
197     backends