1{
2 config,
3 lib,
4 options,
5 pkgs,
6 ...
7}:
8let
9 cfg = config.services.vault;
10 opt = options.services.vault;
11
12 configFile = pkgs.writeText "vault.hcl" ''
13 # vault in dev mode will refuse to start if its configuration sets listener
14 ${lib.optionalString (!cfg.dev) ''
15 listener "tcp" {
16 address = "${cfg.address}"
17 ${
18 if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then
19 ''
20 tls_disable = "true"
21 ''
22 else
23 ''
24 tls_cert_file = "${cfg.tlsCertFile}"
25 tls_key_file = "${cfg.tlsKeyFile}"
26 ''
27 }
28 ${cfg.listenerExtraConfig}
29 }
30 ''}
31 storage "${cfg.storageBackend}" {
32 ${lib.optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
33 ${lib.optionalString (cfg.storageConfig != null) cfg.storageConfig}
34 }
35 ${lib.optionalString (cfg.telemetryConfig != "") ''
36 telemetry {
37 ${cfg.telemetryConfig}
38 }
39 ''}
40 ${cfg.extraConfig}
41 '';
42
43 allConfigPaths = [ configFile ] ++ cfg.extraSettingsPaths;
44 configOptions = lib.escapeShellArgs (
45 lib.optional cfg.dev "-dev"
46 ++ lib.optional (cfg.dev && cfg.devRootTokenID != null) "-dev-root-token-id=${cfg.devRootTokenID}"
47 ++ (lib.concatMap (p: [
48 "-config"
49 p
50 ]) allConfigPaths)
51 );
52
53in
54
55{
56 options = {
57 services.vault = {
58 enable = lib.mkEnableOption "Vault daemon";
59
60 package = lib.mkPackageOption pkgs "vault" { };
61
62 dev = lib.mkOption {
63 type = lib.types.bool;
64 default = false;
65 description = ''
66 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.
67 '';
68 };
69
70 devRootTokenID = lib.mkOption {
71 type = lib.types.nullOr lib.types.str;
72 default = null;
73 description = ''
74 Initial root token. This only applies when {option}`services.vault.dev` is true
75 '';
76 };
77
78 address = lib.mkOption {
79 type = lib.types.str;
80 default = "127.0.0.1:8200";
81 description = "The name of the ip interface to listen to";
82 };
83
84 tlsCertFile = lib.mkOption {
85 type = lib.types.nullOr lib.types.str;
86 default = null;
87 example = "/path/to/your/cert.pem";
88 description = "TLS certificate file. TLS will be disabled unless this option is set";
89 };
90
91 tlsKeyFile = lib.mkOption {
92 type = lib.types.nullOr lib.types.str;
93 default = null;
94 example = "/path/to/your/key.pem";
95 description = "TLS private key file. TLS will be disabled unless this option is set";
96 };
97
98 listenerExtraConfig = lib.mkOption {
99 type = lib.types.lines;
100 default = ''
101 tls_min_version = "tls12"
102 '';
103 description = "Extra text appended to the listener section.";
104 };
105
106 storageBackend = lib.mkOption {
107 type = lib.types.enum [
108 "inmem"
109 "file"
110 "consul"
111 "zookeeper"
112 "s3"
113 "azure"
114 "dynamodb"
115 "etcd"
116 "mssql"
117 "mysql"
118 "postgresql"
119 "swift"
120 "gcs"
121 "raft"
122 ];
123 default = "inmem";
124 description = "The name of the type of storage backend";
125 };
126
127 storagePath = lib.mkOption {
128 type = lib.types.nullOr lib.types.path;
129 default =
130 if cfg.storageBackend == "file" || cfg.storageBackend == "raft" then "/var/lib/vault" else null;
131 defaultText = lib.literalExpression ''
132 if config.${opt.storageBackend} == "file" || cfg.storageBackend == "raft"
133 then "/var/lib/vault"
134 else null
135 '';
136 description = "Data directory for file backend";
137 };
138
139 storageConfig = lib.mkOption {
140 type = lib.types.nullOr lib.types.lines;
141 default = null;
142 description = ''
143 HCL configuration to insert in the storageBackend section.
144
145 Confidential values should not be specified here because this option's
146 value is written to the Nix store, which is publicly readable.
147 Provide credentials and such in a separate file using
148 [](#opt-services.vault.extraSettingsPaths).
149 '';
150 };
151
152 telemetryConfig = lib.mkOption {
153 type = lib.types.lines;
154 default = "";
155 description = "Telemetry configuration";
156 };
157
158 extraConfig = lib.mkOption {
159 type = lib.types.lines;
160 default = "";
161 description = "Extra text appended to {file}`vault.hcl`.";
162 };
163
164 extraSettingsPaths = lib.mkOption {
165 type = lib.types.listOf lib.types.path;
166 default = [ ];
167 description = ''
168 Configuration files to load besides the immutable one defined by the NixOS module.
169 This can be used to avoid putting credentials in the Nix store, which can be read by any user.
170
171 Each path can point to a JSON- or HCL-formatted file, or a directory
172 to be scanned for files with `.hcl` or
173 `.json` extensions.
174
175 To upload the confidential file with NixOps, use for example:
176
177 ```
178 # https://releases.nixos.org/nixops/latest/manual/manual.html#opt-deployment.keys
179 deployment.keys."vault.hcl" = let db = import ./db-credentials.nix; in {
180 text = ${"''"}
181 storage "postgresql" {
182 connection_url = "postgres://''${db.username}:''${db.password}@host.example.com/exampledb?sslmode=verify-ca"
183 }
184 ${"''"};
185 user = "vault";
186 };
187 services.vault.extraSettingsPaths = ["/run/keys/vault.hcl"];
188 services.vault.storageBackend = "postgresql";
189 users.users.vault.extraGroups = ["keys"];
190 ```
191 '';
192 };
193 };
194 };
195
196 config = lib.mkIf cfg.enable {
197 assertions = [
198 {
199 assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
200 message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
201 }
202 {
203 assertion = (
204 (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null))
205 && (cfg.storagePath != null -> (cfg.storageBackend == "file" || cfg.storageBackend == "raft"))
206 );
207 message = ''You must set services.vault.storagePath only when using the "file" or "raft" backend'';
208 }
209 ];
210
211 users.users.vault = {
212 name = "vault";
213 group = "vault";
214 uid = config.ids.uids.vault;
215 description = "Vault daemon user";
216 };
217 users.groups.vault.gid = config.ids.gids.vault;
218
219 systemd.tmpfiles.rules = lib.optional (
220 cfg.storagePath != null
221 ) "d '${cfg.storagePath}' 0700 vault vault - -";
222
223 systemd.services.vault = {
224 description = "Vault server daemon";
225
226 wantedBy = [ "multi-user.target" ];
227 after =
228 [
229 "network.target"
230 ]
231 ++ lib.optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
232
233 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
234
235 startLimitIntervalSec = 60;
236 startLimitBurst = 3;
237 serviceConfig = {
238 User = "vault";
239 Group = "vault";
240 ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
241 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
242 StateDirectory = "vault";
243 # In `dev` mode vault will put its token here
244 Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
245 PrivateDevices = true;
246 PrivateTmp = true;
247 ProtectSystem = "full";
248 ProtectHome = "read-only";
249 AmbientCapabilities = "cap_ipc_lock";
250 NoNewPrivileges = true;
251 LimitCORE = 0;
252 KillSignal = "SIGINT";
253 TimeoutStopSec = "30s";
254 Restart = "on-failure";
255 };
256
257 unitConfig.RequiresMountsFor = lib.optional (cfg.storagePath != null) cfg.storagePath;
258 };
259 };
260
261}