1{ config, lib, options, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.vault;
7 opt = options.services.vault;
8
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) ''
12 listener "tcp" {
13 address = "${cfg.address}"
14 ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
15 tls_disable = "true"
16 '' else ''
17 tls_cert_file = "${cfg.tlsCertFile}"
18 tls_key_file = "${cfg.tlsKeyFile}"
19 ''}
20 ${cfg.listenerExtraConfig}
21 }
22 ''}
23 storage "${cfg.storageBackend}" {
24 ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
25 ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
26 }
27 ${optionalString (cfg.telemetryConfig != "") ''
28 telemetry {
29 ${cfg.telemetryConfig}
30 }
31 ''}
32 ${cfg.extraConfig}
33 '';
34
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));
40
41in
42
43{
44 options = {
45 services.vault = {
46 enable = mkEnableOption "Vault daemon";
47
48 package = mkPackageOption pkgs "vault" { };
49
50 dev = mkOption {
51 type = types.bool;
52 default = false;
53 description = ''
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.
55 '';
56 };
57
58 devRootTokenID = mkOption {
59 type = types.nullOr types.str;
60 default = null;
61 description = ''
62 Initial root token. This only applies when {option}`services.vault.dev` is true
63 '';
64 };
65
66 address = mkOption {
67 type = types.str;
68 default = "127.0.0.1:8200";
69 description = "The name of the ip interface to listen to";
70 };
71
72 tlsCertFile = mkOption {
73 type = types.nullOr types.str;
74 default = null;
75 example = "/path/to/your/cert.pem";
76 description = "TLS certificate file. TLS will be disabled unless this option is set";
77 };
78
79 tlsKeyFile = mkOption {
80 type = types.nullOr types.str;
81 default = null;
82 example = "/path/to/your/key.pem";
83 description = "TLS private key file. TLS will be disabled unless this option is set";
84 };
85
86 listenerExtraConfig = mkOption {
87 type = types.lines;
88 default = ''
89 tls_min_version = "tls12"
90 '';
91 description = "Extra text appended to the listener section.";
92 };
93
94 storageBackend = mkOption {
95 type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" "raft" ];
96 default = "inmem";
97 description = "The name of the type of storage backend";
98 };
99
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"
106 else null
107 '';
108 description = "Data directory for file backend";
109 };
110
111 storageConfig = mkOption {
112 type = types.nullOr types.lines;
113 default = null;
114 description = ''
115 HCL configuration to insert in the storageBackend section.
116
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).
121 '';
122 };
123
124 telemetryConfig = mkOption {
125 type = types.lines;
126 default = "";
127 description = "Telemetry configuration";
128 };
129
130 extraConfig = mkOption {
131 type = types.lines;
132 default = "";
133 description = "Extra text appended to {file}`vault.hcl`.";
134 };
135
136 extraSettingsPaths = mkOption {
137 type = types.listOf types.path;
138 default = [];
139 description = ''
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.
142
143 Each path can point to a JSON- or HCL-formatted file, or a directory
144 to be scanned for files with `.hcl` or
145 `.json` extensions.
146
147 To upload the confidential file with NixOps, use for example:
148
149 ```
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 {
152 text = ${"''"}
153 storage "postgresql" {
154 connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
155 }
156 ${"''"};
157 user = "vault";
158 };
159 services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
160 services.vault.storageBackend = "postgresql";
161 users.users.vault.extraGroups = ["keys"];
162 ```
163 '';
164 };
165 };
166 };
167
168 config = mkIf cfg.enable {
169 assertions = [
170 {
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'';
173 }
174 {
175 assertion = (
176 (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) &&
177 (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
178 );
179 message = ''You must set services.vault.storagePath only when using the "file" or "raft" backend'';
180 }
181 ];
182
183 users.users.vault = {
184 name = "vault";
185 group = "vault";
186 uid = config.ids.uids.vault;
187 description = "Vault daemon user";
188 };
189 users.groups.vault.gid = config.ids.gids.vault;
190
191 systemd.tmpfiles.rules = optional (cfg.storagePath != null)
192 "d '${cfg.storagePath}' 0700 vault vault - -";
193
194 systemd.services.vault = {
195 description = "Vault server daemon";
196
197 wantedBy = ["multi-user.target"];
198 after = [ "network.target" ]
199 ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
200
201 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
202
203 startLimitIntervalSec = 60;
204 startLimitBurst = 3;
205 serviceConfig = {
206 User = "vault";
207 Group = "vault";
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;
214 PrivateTmp = true;
215 ProtectSystem = "full";
216 ProtectHome = "read-only";
217 AmbientCapabilities = "cap_ipc_lock";
218 NoNewPrivileges = true;
219 LimitCORE = 0;
220 KillSignal = "SIGINT";
221 TimeoutStopSec = "30s";
222 Restart = "on-failure";
223 };
224
225 unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
226 };
227 };
228
229}