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 (lib.mdDoc "Vault daemon");
51 defaultText = literalExpression "pkgs.vault";
52 description = lib.mdDoc "This option specifies the vault package to use.";
58 description = lib.mdDoc ''
59 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.
63 devRootTokenID = mkOption {
66 description = lib.mdDoc ''
67 Initial root token. This only applies when {option}`services.vault.dev` is true
73 default = "127.0.0.1:8200";
74 description = lib.mdDoc "The name of the ip interface to listen to";
77 tlsCertFile = mkOption {
78 type = types.nullOr types.str;
80 example = "/path/to/your/cert.pem";
81 description = lib.mdDoc "TLS certificate file. TLS will be disabled unless this option is set";
84 tlsKeyFile = mkOption {
85 type = types.nullOr types.str;
87 example = "/path/to/your/key.pem";
88 description = lib.mdDoc "TLS private key file. TLS will be disabled unless this option is set";
91 listenerExtraConfig = mkOption {
94 tls_min_version = "tls12"
96 description = lib.mdDoc "Extra text appended to the listener section.";
99 storageBackend = mkOption {
100 type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
102 description = lib.mdDoc "The name of the type of storage backend";
105 storagePath = mkOption {
106 type = types.nullOr types.path;
107 default = if cfg.storageBackend == "file" || cfg.storageBackend == "raft" then "/var/lib/vault" else null;
108 defaultText = literalExpression ''
109 if config.${opt.storageBackend} == "file" || cfg.storageBackend == "raft"
110 then "/var/lib/vault"
113 description = lib.mdDoc "Data directory for file backend";
116 storageConfig = mkOption {
117 type = types.nullOr types.lines;
119 description = lib.mdDoc ''
120 HCL configuration to insert in the storageBackend section.
122 Confidential values should not be specified here because this option's
123 value is written to the Nix store, which is publicly readable.
124 Provide credentials and such in a separate file using
125 [](#opt-services.vault.extraSettingsPaths).
129 telemetryConfig = mkOption {
132 description = lib.mdDoc "Telemetry configuration";
135 extraConfig = mkOption {
138 description = lib.mdDoc "Extra text appended to {file}`vault.hcl`.";
141 extraSettingsPaths = mkOption {
142 type = types.listOf types.path;
144 description = lib.mdDoc ''
145 Configuration files to load besides the immutable one defined by the NixOS module.
146 This can be used to avoid putting credentials in the Nix store, which can be read by any user.
148 Each path can point to a JSON- or HCL-formatted file, or a directory
149 to be scanned for files with `.hcl` or
152 To upload the confidential file with NixOps, use for example:
155 # https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
156 deployment.keys."vault.hcl" = let db = import ./db-credentials.nix; in {
158 storage "postgresql" {
159 connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
164 services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
165 services.vault.storageBackend = "postgresql";
166 users.users.vault.extraGroups = ["keys"];
173 config = mkIf cfg.enable {
176 assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
177 message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
181 (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) &&
182 (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
184 message = ''You must set services.vault.storagePath only when using the "file" or "raft" backend'';
188 users.users.vault = {
191 uid = config.ids.uids.vault;
192 description = "Vault daemon user";
194 users.groups.vault.gid = config.ids.gids.vault;
196 systemd.tmpfiles.rules = optional (cfg.storagePath != null)
197 "d '${cfg.storagePath}' 0700 vault vault - -";
199 systemd.services.vault = {
200 description = "Vault server daemon";
202 wantedBy = ["multi-user.target"];
203 after = [ "network.target" ]
204 ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
206 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
208 startLimitIntervalSec = 60;
213 ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
214 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
215 StateDirectory = "vault";
216 # In `dev` mode vault will put its token here
217 Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
218 PrivateDevices = true;
220 ProtectSystem = "full";
221 ProtectHome = "read-only";
222 AmbientCapabilities = "cap_ipc_lock";
223 NoNewPrivileges = true;
224 KillSignal = "SIGINT";
225 TimeoutStopSec = "30s";
226 Restart = "on-failure";
229 unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;