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 "network.target"
229 ]
230 ++ lib.optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
231
232 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
233
234 startLimitIntervalSec = 60;
235 startLimitBurst = 3;
236 serviceConfig = {
237 User = "vault";
238 Group = "vault";
239 ExecStart = "${cfg.package}/bin/vault server ${configOptions}";
240 ExecReload = "${pkgs.coreutils}/bin/kill -SIGHUP $MAINPID";
241 StateDirectory = "vault";
242 # In `dev` mode vault will put its token here
243 Environment = lib.optional (cfg.dev) "HOME=/var/lib/vault";
244 PrivateDevices = true;
245 PrivateTmp = true;
246 ProtectSystem = "full";
247 ProtectHome = "read-only";
248 AmbientCapabilities = "cap_ipc_lock";
249 NoNewPrivileges = true;
250 LimitCORE = 0;
251 KillSignal = "SIGINT";
252 TimeoutStopSec = "30s";
253 Restart = "on-failure";
254 };
255
256 unitConfig.RequiresMountsFor = lib.optional (cfg.storagePath != null) cfg.storagePath;
257 };
258 };
259
260}