1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.vault;
7
8 configFile = pkgs.writeText "vault.hcl" ''
9 listener "tcp" {
10 address = "${cfg.address}"
11 ${if (cfg.tlsCertFile == null || cfg.tlsKeyFile == null) then ''
12 tls_disable = "true"
13 '' else ''
14 tls_cert_file = "${cfg.tlsCertFile}"
15 tls_key_file = "${cfg.tlsKeyFile}"
16 ''}
17 ${cfg.listenerExtraConfig}
18 }
19 storage "${cfg.storageBackend}" {
20 ${optionalString (cfg.storagePath != null) ''path = "${cfg.storagePath}"''}
21 ${optionalString (cfg.storageConfig != null) cfg.storageConfig}
22 }
23 ${optionalString (cfg.telemetryConfig != "") ''
24 telemetry {
25 ${cfg.telemetryConfig}
26 }
27 ''}
28 ${cfg.extraConfig}
29 '';
30in
31
32{
33 options = {
34 services.vault = {
35 enable = mkEnableOption "Vault daemon";
36
37 package = mkOption {
38 type = types.package;
39 default = pkgs.vault;
40 defaultText = "pkgs.vault";
41 description = "This option specifies the vault package to use.";
42 };
43
44 address = mkOption {
45 type = types.str;
46 default = "127.0.0.1:8200";
47 description = "The name of the ip interface to listen to";
48 };
49
50 tlsCertFile = mkOption {
51 type = types.nullOr types.str;
52 default = null;
53 example = "/path/to/your/cert.pem";
54 description = "TLS certificate file. TLS will be disabled unless this option is set";
55 };
56
57 tlsKeyFile = mkOption {
58 type = types.nullOr types.str;
59 default = null;
60 example = "/path/to/your/key.pem";
61 description = "TLS private key file. TLS will be disabled unless this option is set";
62 };
63
64 listenerExtraConfig = mkOption {
65 type = types.lines;
66 default = ''
67 tls_min_version = "tls12"
68 '';
69 description = "Extra text appended to the listener section.";
70 };
71
72 storageBackend = mkOption {
73 type = types.enum [ "inmem" "file" "consul" "zookeeper" "s3" "azure" "dynamodb" "etcd" "mssql" "mysql" "postgresql" "swift" "gcs" ];
74 default = "inmem";
75 description = "The name of the type of storage backend";
76 };
77
78 storagePath = mkOption {
79 type = types.nullOr types.path;
80 default = if cfg.storageBackend == "file" then "/var/lib/vault" else null;
81 description = "Data directory for file backend";
82 };
83
84 storageConfig = mkOption {
85 type = types.nullOr types.lines;
86 default = null;
87 description = "Storage configuration";
88 };
89
90 telemetryConfig = mkOption {
91 type = types.lines;
92 default = "";
93 description = "Telemetry configuration";
94 };
95
96 extraConfig = mkOption {
97 type = types.lines;
98 default = "";
99 description = "Extra text appended to <filename>vault.hcl</filename>.";
100 };
101 };
102 };
103
104 config = mkIf cfg.enable {
105 assertions = [
106 { assertion = cfg.storageBackend == "inmem" -> (cfg.storagePath == null && cfg.storageConfig == null);
107 message = ''The "inmem" storage expects no services.vault.storagePath nor services.vault.storageConfig'';
108 }
109 { assertion = (cfg.storageBackend == "file" -> (cfg.storagePath != null && cfg.storageConfig == null)) && (cfg.storagePath != null -> cfg.storageBackend == "file");
110 message = ''You must set services.vault.storagePath only when using the "file" backend'';
111 }
112 ];
113
114 users.users.vault = {
115 name = "vault";
116 group = "vault";
117 uid = config.ids.uids.vault;
118 description = "Vault daemon user";
119 };
120 users.groups.vault.gid = config.ids.gids.vault;
121
122 systemd.services.vault = {
123 description = "Vault server daemon";
124
125 wantedBy = ["multi-user.target"];
126 after = [ "network.target" ]
127 ++ optional (config.services.consul.enable && cfg.storageBackend == "consul") "consul.service";
128
129 restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
130
131 preStart = optionalString (cfg.storagePath != null) ''
132 install -d -m0700 -o vault -g vault "${cfg.storagePath}"
133 '';
134
135 serviceConfig = {
136 User = "vault";
137 Group = "vault";
138 PermissionsStartOnly = true;
139 ExecStart = "${cfg.package}/bin/vault server -config ${configFile}";
140 PrivateDevices = true;
141 PrivateTmp = true;
142 ProtectSystem = "full";
143 ProtectHome = "read-only";
144 AmbientCapabilities = "cap_ipc_lock";
145 NoNewPrivileges = true;
146 KillSignal = "SIGINT";
147 TimeoutStopSec = "30s";
148 Restart = "on-failure";
149 StartLimitInterval = "60s";
150 StartLimitBurst = 3;
151 };
152
153 unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
154 };
155 };
156
157}