{ config, lib, pkgs, ... }: with lib; let cfg = config.services.vault; configFile = pkgs.writeText "vault.hcl" '' listener "tcp" { address = "${cfg.address}" ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then '' tls_disable = "true" '' else '' tls_cert_file = "${cfg.tlsCertFile}" tls_key_file = "${cfg.tlsKeyFile}" ''} ${cfg.listenerExtraConfig} } storage "${cfg.storageBackend}" { ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''} ${optionalString (cfg.storageConfig != null) cfg.storageConfig} } ${optionalString (cfg.telemetryConfig != "") '' telemetry { ${cfg.telemetryConfig} } ''} ${cfg.extraConfig} ''; in { options = { services.vault = { enable = mkEnableOption "Vault daemon"; package = mkOption { type = types.package; default = pkgs.vault; defaultText = "pkgs.vault"; description = "This option specifies the vault package to use."; }; address = mkOption { type = types.str; default = "127.0.0.1:8200"; description = "The name of the ip interface to listen to"; }; tlsCertFile = mkOption { type = types.nullOr types.str; default = null; example = "/path/to/your/cert.pem"; description = "TLS certificate file. TLS will be disabled unless this option is set"; }; tlsKeyFile = mkOption { type = types.nullOr types.str; default = null; example = "/path/to/your/key.pem"; description = "TLS private key file. TLS will be disabled unless this option is set"; }; listenerExtraConfig = mkOption { type = types.lines; default = '' tls_min_version = "tls12" ''; description = "Extra text appended to the listener section."; }; storageBackend = mkOption { type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ]; default = "inmem"; description = "The name of the type of storage backend"; }; storagePath = mkOption { type = types.nullOr types.path; default = if cfg.storageBackend == "file" then "/var/lib/vault" else null; description = "Data directory for file backend"; }; storageConfig = mkOption { type = types.nullOr types.lines; default = null; description = "Storage configuration"; }; telemetryConfig = mkOption { type = types.lines; default = ""; description = "Telemetry configuration"; }; extraConfig = mkOption { type = types.lines; default = ""; description = "Extra text appended to vault.hcl."; }; }; }; config = mkIf cfg.enable { assertions = [ { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null); message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig''; } { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file"); message = ''You must set services.vault.storagePath only when using the "file" backend''; } ]; users.users.vault = { name = "vault"; group = "vault"; uid = config.ids.uids.vault; description = "Vault daemon user"; }; users.groups.vault.gid = config.ids.gids.vault; systemd.tmpfiles.rules = optional (cfg.storagePath != null) [ "d '${cfg.storagePath}' 0700 vault vault - -" ]; systemd.services.vault = { description = "Vault server daemon"; wantedBy = ["multi-user.target"]; after = [ "network.target" ] ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service"; restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients. serviceConfig = { User = "vault"; Group = "vault"; ExecStart = "${cfg.package}/bin/vault server -config ${configFile}"; PrivateDevices = true; PrivateTmp = true; ProtectSystem = "full"; ProtectHome = "read-only"; AmbientCapabilities = "cap_ipc_lock"; NoNewPrivileges = true; KillSignal = "SIGINT"; TimeoutStopSec = "30s"; Restart = "on-failure"; StartLimitInterval = "60s"; StartLimitBurst = 3; }; unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath; }; }; }