1 { config, lib, options, pkgs, ... }:
6 cfg = config.services.vault;
7 opt = options.services.vault;
9 configFile = pkgs.writeText "vault.hcl" ''
10 # vault in dev mode will refuse to start if its configuration sets listener
11 ${lib.optionalString (!cfg.dev) ''
13 address = "${cfg.address}"
14 ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
17 tls_cert_file = "${cfg.tlsCertFile}"
18 tls_key_file = "${cfg.tlsKeyFile}"
20 ${cfg.listenerExtraConfig}
23 storage "${cfg.storageBackend}" {
24 ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
25 ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
27 ${optionalString (cfg.telemetryConfig != "") ''
29 ${cfg.telemetryConfig}
35 allConfigPaths = [configFile] ++ cfg.extraSettingsPaths;
36 configOptions = escapeShellArgs
37 (lib.optional cfg.dev "-dev" ++
38 lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}"
39 ++ (concatMap (p: ["-config" p]) allConfigPaths));
46 enable = mkEnableOption "Vault daemon";
48 package = mkPackageOption pkgs "vault" { };
54 In this mode, Vault runs in-memory and starts unsealed. This option is not meant production but for development and testing i.e. for nixos tests.
58 devRootTokenID = mkOption {
59 type = types.nullOr types.str;
62 Initial root token. This only applies when {option}`services.vault.dev` is true
68 default = "127.0.0.1:8200";
69 description = "The name of the ip interface to listen to";
72 tlsCertFile = mkOption {
73 type = types.nullOr types.str;
75 example = "/path/to/your/cert.pem";
76 description = "TLS certificate file. TLS will be disabled unless this option is set";
79 tlsKeyFile = mkOption {
80 type = types.nullOr types.str;
82 example = "/path/to/your/key.pem";
83 description = "TLS private key file. TLS will be disabled unless this option is set";
86 listenerExtraConfig = mkOption {
89 tls_min_version = "tls12"
91 description = "Extra text appended to the listener section.";
94 storageBackend = mkOption {
95 type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
97 description = "The name of the type of storage backend";
100 storagePath = mkOption {
101 type = types.nullOr types.path;
102 default = if cfg.storageBackend == "file" || cfg.storageBackend == "raft" then "/var/lib/vault" else null;
103 defaultText = literalExpression ''
104 if config.${opt.storageBackend} == "file" || cfg.storageBackend == "raft"
105 then "/var/lib/vault"
108 description = "Data directory for file backend";
111 storageConfig = mkOption {
112 type = types.nullOr types.lines;
115 HCL configuration to insert in the storageBackend section.
117 Confidential values should not be specified here because this option's
118 value is written to the Nix store, which is publicly readable.
119 Provide credentials and such in a separate file using
120 [](#opt-services.vault.extraSettingsPaths).
124 telemetryConfig = mkOption {
127 description = "Telemetry configuration";
130 extraConfig = mkOption {
133 description = "Extra text appended to {file}`vault.hcl`.";
136 extraSettingsPaths = mkOption {
137 type = types.listOf types.path;
140 Configuration files to load besides the immutable one defined by the NixOS module.
141 This can be used to avoid putting credentials in the Nix store, which can be read by any user.
143 Each path can point to a JSON- or HCL-formatted file, or a directory
144 to be scanned for files with `.hcl` or
147 To upload the confidential file with NixOps, use for example:
150 # https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
151 deployment.keys."vault.hcl" = let db = import ./db-credentials.nix; in {
153 storage "postgresql" {
154 connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
159 services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
160 services.vault.storageBackend = "postgresql";
161 users.users.vault.extraGroups = ["keys"];
168 config = mkIf cfg.enable {
171 assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
172 message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
176 (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) &&
177 (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
179 message = ''You must set services.vault.storagePath only when using the "file" or "raft" backend'';
183 users.users.vault = {
186 uid = config.ids.uids.vault;
187 description = "Vault daemon user";
189 users.groups.vault.gid = config.ids.gids.vault;
191 systemd.tmpfiles.rules = optional (cfg.storagePath != null)
192 "d '${cfg.storagePath}' 0700 vault vault - -";
194 systemd.services.vault = {
195 description = "Vault server daemon";
197 wantedBy = ["multi-user.target"];
198 after = [ "network.target" ]
199 ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
201 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
203 startLimitIntervalSec = 60;
208 ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
209 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
210 StateDirectory = "vault";
211 # In `dev` mode vault will put its token here
212 Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
213 PrivateDevices = true;
215 ProtectSystem = "full";
216 ProtectHome = "read-only";
217 AmbientCapabilities = "cap_ipc_lock";
218 NoNewPrivileges = true;
220 KillSignal = "SIGINT";
221 TimeoutStopSec = "30s";
222 Restart = "on-failure";
225 unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;