diff --git a/README.md b/README.md index cb301dc..5952b3a 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ services. Also, the design will be extendable to allow users to add services not - [ ] Log slow requests. - [X] SSL support. - [X] Backup support. +- [X] Vaultwarden + - [X] UI only accessible for `vaultwarden_user` LDAP group. + - [X] `/admin` only accessible for `vaultwarden_admin` LDAP group. + - [WIP] True SSO support, see [dani-garcia/vaultwarden/issues/246](https://github.com/dani-garcia/vaultwarden/issues/246). For now, Authelia protects access to the UI but you need to login afterwards to Vaultwarden. So there are two login required. - [X] Nextcloud - [ ] Export metrics to Prometheus. - [ ] Export traces to Prometheus. diff --git a/flake.nix b/flake.nix index 881b795..42fb077 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,7 @@ modules/postgresql.nix modules/ssl.nix modules/tinyproxy.nix + modules/vaultwarden.nix modules/vpn.nix ]; }; diff --git a/modules/vaultwarden.nix b/modules/vaultwarden.nix new file mode 100644 index 0000000..913a638 --- /dev/null +++ b/modules/vaultwarden.nix @@ -0,0 +1,209 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.shb.vaultwarden; + + fqdn = "${cfg.subdomain}.${cfg.domain}"; + + template = file: newPath: replacements: + let + templatePath = newPath + ".template"; + + sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements); + in + '' + ln -fs ${file} ${templatePath} + rm ${newPath} || : + sed ${sedPatterns} ${templatePath} > ${newPath} + ''; +in +{ + options.shb.vaultwarden = { + enable = lib.mkEnableOption "selfhostblocks.vaultwarden"; + + subdomain = lib.mkOption { + type = lib.types.str; + description = "Subdomain under which Authelia will be served."; + example = "ha"; + }; + + domain = lib.mkOption { + type = lib.types.str; + description = "domain under which Authelia will be served."; + example = "mydomain.com"; + }; + + port = lib.mkOption { + type = lib.types.port; + description = "Port on which vaultwarden service listens."; + default = 8222; + }; + + ldapEndpoint = lib.mkOption { + type = lib.types.str; + description = "Endpoint for LDAP authentication backend."; + example = "ldap.example.com"; + }; + + oidcEndpoint = lib.mkOption { + type = lib.types.str; + description = "OIDC endpoint for SSO"; + example = "https://authelia.example.com"; + }; + + databasePasswordFile = lib.mkOption { + type = lib.types.str; + description = "File containing the password to connect to the postgresql database."; + }; + + smtp = lib.mkOption { + type = lib.types.submodule { + options = { + from_address = lib.mkOption { + type = lib.types.str; + description = "SMTP address from which the emails originate."; + example = "vaultwarden@mydomain.com"; + }; + from_name = lib.mkOption { + type = lib.types.str; + description = "SMTP name from which the emails originate."; + default = "Vaultwarden"; + }; + host = lib.mkOption { + type = lib.types.str; + description = "SMTP host to send the emails to."; + }; + security = lib.mkOption { + type = lib.types.enum [ "starttls" "force_tls" "off" ]; + description = "Security expected by SMTP host."; + default = "starttls"; + }; + port = lib.mkOption { + type = lib.types.port; + description = "SMTP port to send the emails to."; + default = 25; + }; + username = lib.mkOption { + type = lib.types.str; + description = "Username to connect to the SMTP host."; + }; + auth_mechanism = lib.mkOption { + type = lib.types.enum [ "Login" ]; + description = "Auth mechanism."; + default = "Login"; + }; + passwordFile = lib.mkOption { + type = lib.types.str; + description = "File containing the password to connect to the SMTP host."; + }; + }; + }; + }; + + backupConfig = lib.mkOption { + type = lib.types.nullOr lib.types.anything; + default = null; + }; + + debug = lib.mkOption { + type = lib.types.bool; + description = "Set to true to enable debug logging."; + default = false; + example = true; + }; + }; + + config = lib.mkIf cfg.enable { + services.vaultwarden = { + enable = true; + dbBackend = "postgresql"; + config = { + DATA_FOLDER = "/var/lib/bitwarden_rs"; + IP_HEADER = "X-Real-IP"; + SIGNUPS_ALLOWED = false; + # Disabled because the /admin path is protected by SSO + DISABLE_ADMIN_TOKEN = true; + INVITATIONS_ALLOWED = true; + DOMAIN = "https://${fqdn}"; + USE_SYSLOG = true; + EXTENDED_LOGGING = cfg.debug; + LOG_LEVEL = if cfg.debug then "trace" else "info"; + ROCKET_LOG = if cfg.debug then "trace" else "info"; + ROCKET_ADDRESS = "127.0.0.1"; + ROCKET_PORT = cfg.port; + + SMTP_FROM = cfg.smtp.from_address; + SMTP_FROM_NAME = cfg.smtp.from_name; + SMTP_HOST = cfg.smtp.host; + SMTP_SECURITY = cfg.smtp.security; + SMTP_USERNAME = cfg.smtp.username; + SMTP_PORT = cfg.smtp.port; + SMTP_AUTH_MECHANISM = cfg.smtp.auth_mechanism; + }; + environmentFile = "/var/lib/bitwarden_rs/vaultwarden.env"; + }; + # We create a blank environment file for the service to start. Then, ExecPreStart kicks in and + # fills out the environment file for ExecStart to pick it up. + systemd.tmpfiles.rules = [ + "d /var/lib/bitwarden_rs 0750 vaultwarden vaultwarden" + "f /var/lib/bitwarden_rs/vaultwarden.env 0640 vaultwarden vaultwarden" + ]; + systemd.services.vaultwarden.preStart = + let + envFile = pkgs.writeText "vaultwarden.env" '' + DATABASE_URL=postgresql://vaultwarden:%DB_PASSWORD%@127.0.0.1:5432/vaultwarden + SMTP_PASSWORD=%SMTP_PASSWORD% + ''; + in + template envFile "/var/lib/bitwarden_rs/vaultwarden.env" { + "%DB_PASSWORD%" = "$(cat ${cfg.databasePasswordFile})"; + "%SMTP_PASSWORD%" = "$(cat ${cfg.smtp.passwordFile})"; + }; + + shb.nginx.autheliaProtect = [ + { + inherit (cfg) subdomain domain oidcEndpoint; + upstream = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}"; + autheliaRules = [ + { + domain = "${fqdn}/admin"; + policy = "two_factor"; + subject = ["group:vaultwarden_admin"]; + } + { + domain = fqdn; + policy = "two_factor"; + subject = ["group:vaultwarden"]; + } + ]; + } + ]; + + shb.postgresql.tcpIPPort= 5432; + shb.postgresql.passwords = [ + { + username = "vaultwarden"; + database = "vaultwarden"; + passwordFile = cfg.databasePasswordFile; + } + ]; + + systemd.services.vaultwarden.serviceConfig.UMask = lib.mkForce "0027"; + # systemd.services.vaultwarden.serviceConfig.Group = lib.mkForce "media"; + users.users.deluge = { + extraGroups = [ "media" ]; + }; + + users.groups.vaultwarden = { + members = [ "backup" ]; + }; + + shb.backup.instances.vaultwarden = + cfg.backupConfig // + { + sourceDirectories = [ + config.services.vaultwarden.backupDir + ]; + }; + }; +}