1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 mkEnableOption
11 mkIf
12 mkOption
13 mkPackageOption
14 types
15 ;
16
17 cfg = config.services.tlsrpt;
18
19 format = pkgs.formats.ini { };
20 dropNullValues = lib.filterAttrsRecursive (_: value: value != null);
21
22 commonServiceSettings = {
23 DynamicUser = true;
24 User = "tlsrpt";
25 Restart = "always";
26 StateDirectory = "tlsrpt";
27 StateDirectoryMode = "0700";
28
29 # Hardening
30 CapabilityBoundingSet = [ "" ];
31 LockPersonality = true;
32 MemoryDenyWriteExecute = true;
33 PrivateDevices = true;
34 PrivateUsers = false;
35 ProcSubset = "pid";
36 ProtectControlGroups = true;
37 ProtectClock = true;
38 ProtectHome = true;
39 ProtectHostname = true;
40 ProtectKernelLogs = true;
41 ProtectKernelModules = true;
42 ProtectKernelTunables = true;
43 ProtectProc = "noaccess";
44 RestrictNamespaces = true;
45 RestrictRealtime = true;
46 SystemCallArchitectures = "native";
47 SystemCallFilter = [
48 "@system-service"
49 "~@privileged @resources"
50 ];
51 };
52
53 collectdConfigFile = format.generate "tlsrpt-collectd.cfg" {
54 tlsrpt_collectd = dropNullValues cfg.collectd.settings;
55 };
56 fetcherConfigFile = format.generate "tlsrpt-fetcher.cfg" {
57 tlsrpt_fetcher = dropNullValues cfg.fetcher.settings;
58 };
59 reportdConfigFile = format.generate "tlsrpt-reportd.cfg" {
60 tlsrpt_reportd = dropNullValues cfg.reportd.settings;
61 };
62
63 withPostfix = config.services.postfix.enable && cfg.configurePostfix;
64in
65
66{
67 options.services.tlsrpt = {
68 enable = mkEnableOption "the TLSRPT services";
69
70 package = mkPackageOption pkgs "tlsrpt-reporter" { };
71
72 collectd = {
73 settings = mkOption {
74 type = types.submodule {
75 freeformType = format.type;
76 options = {
77 storage = mkOption {
78 type = types.str;
79 default = "sqlite:///var/lib/tlsrpt/collectd.sqlite";
80 description = ''
81 Storage backend definition.
82 '';
83 };
84
85 socketname = mkOption {
86 type = types.path;
87 default = "/run/tlsrpt/collectd.sock";
88 description = ''
89 Path at which the UNIX socket will be created.
90 '';
91 };
92
93 socketmode = mkOption {
94 type = types.str;
95 default = "0220";
96 description = ''
97 Permissions on the UNIX socket.
98 '';
99 };
100
101 log_level = mkOption {
102 type = types.enum [
103 "debug"
104 "info"
105 "warning"
106 "error"
107 "critical"
108 ];
109 default = "info";
110 description = ''
111 Level of log messages to emit.
112 '';
113 };
114 };
115 };
116 default = { };
117 description = ''
118 Flags from {manpage}`tlsrpt-collectd(1)` as key-value pairs.
119 '';
120 };
121
122 extraFlags = mkOption {
123 type = with types; listOf str;
124 default = [ ];
125 description = ''
126 List of extra flags to pass to the tlsrpt-reportd executable.
127
128 See {manpage}`tlsrpt-collectd(1)` for possible flags.
129 '';
130 };
131 };
132
133 fetcher = {
134 settings = mkOption {
135 type = types.submodule {
136 freeformType = format.type;
137 options = {
138 storage = mkOption {
139 type = types.str;
140 default = config.services.tlsrpt.collectd.settings.storage;
141 defaultText = lib.literalExpression ''
142 config.services.tlsrpt.collectd.settings.storage
143 '';
144 description = ''
145 Path to the collectd sqlite database.
146 '';
147 };
148
149 log_level = mkOption {
150 type = types.enum [
151 "debug"
152 "info"
153 "warning"
154 "error"
155 "critical"
156 ];
157 default = "info";
158 description = ''
159 Level of log messages to emit.
160 '';
161 };
162 };
163 };
164 default = { };
165 description = ''
166 Flags from {manpage}`tlsrpt-fetcher(1)` as key-value pairs.
167 '';
168 };
169 };
170
171 reportd = {
172 settings = mkOption {
173 type = types.submodule {
174 freeformType = format.type;
175 options = {
176 dbname = mkOption {
177 type = types.str;
178 default = "/var/lib/tlsrpt/reportd.sqlite";
179 description = ''
180 Path to the sqlite database.
181 '';
182 };
183
184 fetchers = mkOption {
185 type = types.str;
186 default = lib.getExe' cfg.package "tlsrpt-fetcher";
187 defaultText = lib.literalExpression ''
188 lib.getExe' cfg.package "tlsrpt-fetcher"
189 '';
190 description = ''
191 Comma-separated list of fetcher programs that retrieve collectd data.
192 '';
193 };
194
195 log_level = mkOption {
196 type = types.enum [
197 "debug"
198 "info"
199 "warning"
200 "error"
201 "critical"
202 ];
203 default = "info";
204 description = ''
205 Level of log messages to emit.
206 '';
207 };
208
209 organization_name = mkOption {
210 type = types.str;
211 example = "ACME Corp.";
212 description = ''
213 Name of the organization sending out the reports.
214 '';
215 };
216
217 contact_info = mkOption {
218 type = types.str;
219 example = "smtp-tls-reporting@example.com";
220 description = ''
221 Contact information embedded into the reports.
222 '';
223 };
224
225 http_script = mkOption {
226 type = with types; nullOr str;
227 default = "${lib.getExe pkgs.curl} --silent --header 'Content-Type: application/tlsrpt+gzip' --data-binary @-";
228 defaultText = lib.literalExpression ''
229 ''${lib.getExe pkgs.curl} --silent --header 'Content-Type: application/tlsrpt+gzip' --data-binary @-
230 '';
231 description = ''
232 Call to an HTTPS client, that accepts the URL on the commandline and the request body from stdin.
233 '';
234 };
235
236 sender_address = mkOption {
237 type = types.str;
238 example = "noreply@example.com";
239 description = ''
240 Sender address used for reports.
241 '';
242 };
243
244 sendmail_script = mkOption {
245 type = with types; nullOr str;
246 default =
247 if config.services.postfix.enable && config.services.postfix.setSendmail then
248 "/run/wrappers/bin/sendmail -i -t"
249 else
250 null;
251 defaultText = lib.literalExpression ''
252 if config.services.postfix.enable && config.services.postfix.setSendmail then
253 "/run/wrappers/bin/sendmail -i -t"
254 else
255 null
256 '';
257 description = ''
258 Path to a sendmail-compatible executable for delivery reports.
259 '';
260 };
261 };
262 };
263 default = { };
264 description = ''
265 Flags from {manpage}`tlsrpt-reportd(1)` as key-value pairs.
266 '';
267 };
268
269 extraFlags = mkOption {
270 type = with types; listOf str;
271 default = [ ];
272 description = ''
273 List of extra flags to pass to the tlsrpt-reportd executable.
274
275 See {manpage}`tlsrpt-report(1)` for possible flags.
276 '';
277 };
278 };
279
280 configurePostfix = mkOption {
281 type = types.bool;
282 default = true;
283 description = ''
284 Whether to configure permissions to allow integration with Postfix.
285 '';
286 };
287 };
288
289 config = mkIf cfg.enable {
290 environment.etc = {
291 "tlsrpt/collectd.cfg".source = collectdConfigFile;
292 "tlsrpt/fetcher.cfg".source = fetcherConfigFile;
293 "tlsrpt/reportd.cfg".source = reportdConfigFile;
294 };
295
296 users.users.tlsrpt = {
297 isSystemUser = true;
298 group = "tlsrpt";
299 };
300 users.groups.tlsrpt = { };
301
302 users.users.postfix.extraGroups = lib.mkIf withPostfix [
303 "tlsrpt"
304 ];
305
306 systemd.services.tlsrpt-collectd = {
307 description = "TLSRPT datagram collector";
308 documentation = [ "man:tlsrpt-collectd(1)" ];
309
310 wantedBy = [ "multi-user.target" ];
311
312 restartTriggers = [ collectdConfigFile ];
313
314 serviceConfig = commonServiceSettings // {
315 ExecStart = toString (
316 [
317 (lib.getExe' cfg.package "tlsrpt-collectd")
318 ]
319 ++ cfg.collectd.extraFlags
320 );
321 IPAddressDeny = "any";
322 PrivateNetwork = true;
323 RestrictAddressFamilies = [ "AF_UNIX" ];
324 RuntimeDirectory = "tlsrpt";
325 RuntimeDirectoryMode = "0750";
326 UMask = "0157";
327 };
328 };
329
330 systemd.services.tlsrpt-reportd = {
331 description = "TLSRPT report generator";
332 documentation = [ "man:tlsrpt-reportd(1)" ];
333
334 wantedBy = [ "multi-user.target" ];
335
336 restartTriggers = [ reportdConfigFile ];
337
338 serviceConfig = commonServiceSettings // {
339 ExecStart = toString (
340 [
341 (lib.getExe' cfg.package "tlsrpt-reportd")
342 ]
343 ++ cfg.reportd.extraFlags
344 );
345 RestrictAddressFamilies = [
346 "AF_INET"
347 "AF_INET6"
348 "AF_NETLINK"
349 ];
350 ReadWritePaths = lib.optionals withPostfix [ "/var/lib/postfix/queue/maildrop" ];
351 SupplementaryGroups = lib.optionals withPostfix [ "postdrop" ];
352 UMask = "0077";
353 };
354 };
355 };
356}