python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / security / vaultwarden / default.nix
blob81423e57fd2c3d17513f402103d41d2db03c8493
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.vaultwarden;
7   user = config.users.users.vaultwarden.name;
8   group = config.users.groups.vaultwarden.name;
10   # Convert name from camel case (e.g. disable2FARemember) to upper case snake case (e.g. DISABLE_2FA_REMEMBER).
11   nameToEnvVar = name:
12     let
13       parts = builtins.split "([A-Z0-9]+)" name;
14       partsToEnvVar = parts: foldl' (key: x: let last = stringLength key - 1; in
15         if isList x then key + optionalString (key != "" && substring last 1 key != "_") "_" + head x
16         else if key != "" && elem (substring 0 1 x) lowerChars then # to handle e.g. [ "disable" [ "2FAR" ] "emember" ]
17           substring 0 last key + optionalString (substring (last - 1) 1 key != "_") "_" + substring last 1 key + toUpper x
18         else key + toUpper x) "" parts;
19     in if builtins.match "[A-Z0-9_]+" name != null then name else partsToEnvVar parts;
21   # Due to the different naming schemes allowed for config keys,
22   # we can only check for values consistently after converting them to their corresponding environment variable name.
23   configEnv =
24     let
25       configEnv = listToAttrs (concatLists (mapAttrsToList (name: value:
26         if value != null then [ (nameValuePair (nameToEnvVar name) (if isBool value then boolToString value else toString value)) ] else []
27       ) cfg.config));
28     in { DATA_FOLDER = "/var/lib/bitwarden_rs"; } // optionalAttrs (!(configEnv ? WEB_VAULT_ENABLED) || configEnv.WEB_VAULT_ENABLED == "true") {
29       WEB_VAULT_FOLDER = "${cfg.webVaultPackage}/share/vaultwarden/vault";
30     } // configEnv;
32   configFile = pkgs.writeText "vaultwarden.env" (concatStrings (mapAttrsToList (name: value: "${name}=${value}\n") configEnv));
34   vaultwarden = cfg.package.override { inherit (cfg) dbBackend; };
36 in {
37   imports = [
38     (mkRenamedOptionModule [ "services" "bitwarden_rs" ] [ "services" "vaultwarden" ])
39   ];
41   options.services.vaultwarden = with types; {
42     enable = mkEnableOption (lib.mdDoc "vaultwarden");
44     dbBackend = mkOption {
45       type = enum [ "sqlite" "mysql" "postgresql" ];
46       default = "sqlite";
47       description = lib.mdDoc ''
48         Which database backend vaultwarden will be using.
49       '';
50     };
52     backupDir = mkOption {
53       type = nullOr str;
54       default = null;
55       description = lib.mdDoc ''
56         The directory under which vaultwarden will backup its persistent data.
57       '';
58     };
60     config = mkOption {
61       type = attrsOf (nullOr (oneOf [ bool int str ]));
62       default = {};
63       example = literalExpression ''
64         {
65           DOMAIN = "https://bitwarden.example.com";
66           SIGNUPS_ALLOWED = false;
68           # Vaultwarden currently recommends running behind a reverse proxy
69           # (nginx or similar) for TLS termination, see
70           # https://github.com/dani-garcia/vaultwarden/wiki/Hardening-Guide#reverse-proxying
71           # > you should avoid enabling HTTPS via vaultwarden's built-in Rocket TLS support,
72           # > especially if your instance is publicly accessible.
73           #
74           # A suitable NixOS nginx reverse proxy example config might be:
75           #
76           #     services.nginx.virtualHosts."bitwarden.example.com" = {
77           #       enableACME = true;
78           #       forceSSL = true;
79           #       locations."/" = {
80           #         proxyPass = "http://127.0.0.1:''${toString config.services.vaultwarden.config.ROCKET_PORT}";
81           #       };
82           #     };
83           ROCKET_ADDRESS = "127.0.0.1";
84           ROCKET_PORT = 8222;
86           ROCKET_LOG = "critical";
88           # This example assumes a mailserver running on localhost,
89           # thus without transport encryption.
90           # If you use an external mail server, follow:
91           #   https://github.com/dani-garcia/vaultwarden/wiki/SMTP-configuration
92           SMTP_HOST = "127.0.0.1";
93           SMTP_PORT = 25;
94           SMTP_SSL = false;
96           SMTP_FROM = "admin@bitwarden.example.com";
97           SMTP_FROM_NAME = "example.com Bitwarden server";
98         }
99       '';
100       description = lib.mdDoc ''
101         The configuration of vaultwarden is done through environment variables,
102         therefore it is recommended to use upper snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
104         However, camel case (e.g. `disable2FARemember`) is also supported:
105         The NixOS module will convert it automatically to
106         upper case snake case (e.g. {env}`DISABLE_2FA_REMEMBER`).
107         In this conversion digits (0-9) are handled just like upper case characters,
108         so `foo2` would be converted to {env}`FOO_2`.
109         Names already in this format remain unchanged, so `FOO2` remains `FOO2` if passed as such,
110         even though `foo2` would have been converted to {env}`FOO_2`.
111         This allows working around any potential future conflicting naming conventions.
113         Based on the attributes passed to this config option an environment file will be generated
114         that is passed to vaultwarden's systemd service.
116         The available configuration options can be found in
117         [the environment template file](https://github.com/dani-garcia/vaultwarden/blob/${vaultwarden.version}/.env.template).
119         See ()[#opt-services.vaultwarden.environmentFile) for how
120         to set up access to the Admin UI to invite initial users.
121       '';
122     };
124     environmentFile = mkOption {
125       type = with types; nullOr path;
126       default = null;
127       example = "/var/lib/vaultwarden.env";
128       description = lib.mdDoc ''
129         Additional environment file as defined in {manpage}`systemd.exec(5)`.
131         Secrets like {env}`ADMIN_TOKEN` and {env}`SMTP_PASSWORD`
132         may be passed to the service without adding them to the world-readable Nix store.
134         Note that this file needs to be available on the host on which
135         `vaultwarden` is running.
137         As a concrete example, to make the Admin UI available
138         (from which new users can be invited initially),
139         the secret {env}`ADMIN_TOKEN` needs to be defined as described
140         [here](https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page).
141         Setting `environmentFile` to `/var/lib/vaultwarden.env`
142         and ensuring permissions with e.g.
143         `chown vaultwarden:vaultwarden /var/lib/vaultwarden.env`
144         (the `vaultwarden` user will only exist after activating with
145         `enable = true;` before this), we can set the contents of the file to have
146         contents such as:
148         ```
149         # Admin secret token, see
150         # https://github.com/dani-garcia/vaultwarden/wiki/Enabling-admin-page
151         ADMIN_TOKEN=...copy-paste a unique generated secret token here...
152         ```
153       '';
154     };
156     package = mkOption {
157       type = package;
158       default = pkgs.vaultwarden;
159       defaultText = literalExpression "pkgs.vaultwarden";
160       description = lib.mdDoc "Vaultwarden package to use.";
161     };
163     webVaultPackage = mkOption {
164       type = package;
165       default = pkgs.vaultwarden-vault;
166       defaultText = literalExpression "pkgs.vaultwarden-vault";
167       description = lib.mdDoc "Web vault package to use.";
168     };
169   };
171   config = mkIf cfg.enable {
172     assertions = [ {
173       assertion = cfg.backupDir != null -> cfg.dbBackend == "sqlite";
174       message = "Backups for database backends other than sqlite will need customization";
175     } ];
177     users.users.vaultwarden = {
178       inherit group;
179       isSystemUser = true;
180     };
181     users.groups.vaultwarden = { };
183     systemd.services.vaultwarden = {
184       aliases = [ "bitwarden_rs.service" ];
185       after = [ "network.target" ];
186       path = with pkgs; [ openssl ];
187       serviceConfig = {
188         User = user;
189         Group = group;
190         EnvironmentFile = [ configFile ] ++ optional (cfg.environmentFile != null) cfg.environmentFile;
191         ExecStart = "${vaultwarden}/bin/vaultwarden";
192         LimitNOFILE = "1048576";
193         PrivateTmp = "true";
194         PrivateDevices = "true";
195         ProtectHome = "true";
196         ProtectSystem = "strict";
197         AmbientCapabilities = "CAP_NET_BIND_SERVICE";
198         StateDirectory = "bitwarden_rs";
199         StateDirectoryMode = "0700";
200         Restart = "always";
201       };
202       wantedBy = [ "multi-user.target" ];
203     };
205     systemd.services.backup-vaultwarden = mkIf (cfg.backupDir != null) {
206       aliases = [ "backup-bitwarden_rs.service" ];
207       description = "Backup vaultwarden";
208       environment = {
209         DATA_FOLDER = "/var/lib/bitwarden_rs";
210         BACKUP_FOLDER = cfg.backupDir;
211       };
212       path = with pkgs; [ sqlite ];
213       # if both services are started at the same time, vaultwarden fails with "database is locked"
214       before = [ "vaultwarden.service" ];
215       serviceConfig = {
216         SyslogIdentifier = "backup-vaultwarden";
217         Type = "oneshot";
218         User = mkDefault user;
219         Group = mkDefault group;
220         ExecStart = "${pkgs.bash}/bin/bash ${./backup.sh}";
221       };
222       wantedBy = [ "multi-user.target" ];
223     };
225     systemd.timers.backup-vaultwarden = mkIf (cfg.backupDir != null) {
226       aliases = [ "backup-bitwarden_rs.timer" ];
227       description = "Backup vaultwarden on time";
228       timerConfig = {
229         OnCalendar = mkDefault "23:00";
230         Persistent = "true";
231         Unit = "backup-vaultwarden.service";
232       };
233       wantedBy = [ "multi-user.target" ];
234     };
235   };
237   # uses attributes of the linked package
238   meta.buildDocsInSandbox = false;