vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / monitoring / prometheus / exporters.nix
blob29a30e938e75a1e32e556f62b0eb252610dd141a
1 { config, pkgs, lib, options, utils, ... }:
3 let
4   inherit (lib) concatStrings foldl foldl' genAttrs literalExpression maintainers
5     mapAttrs mapAttrsToList mkDefault mkEnableOption mkIf mkMerge mkOption
6     optional types mkOptionDefault flip attrNames xor;
8   cfg = config.services.prometheus.exporters;
10   # each attribute in `exporterOpts` is expected to have specified:
11   #   - port        (types.int):   port on which the exporter listens
12   #   - serviceOpts (types.attrs): config that is merged with the
13   #                                default definition of the exporter's
14   #                                systemd service
15   #   - extraOpts   (types.attrs): extra configuration options to
16   #                                configure the exporter with, which
17   #                                are appended to the default options
18   #
19   #  Note that `extraOpts` is optional, but a script for the exporter's
20   #  systemd service must be provided by specifying either
21   #  `serviceOpts.script` or `serviceOpts.serviceConfig.ExecStart`
23   exporterOpts = (genAttrs [
24     "apcupsd"
25     "artifactory"
26     "bind"
27     "bird"
28     "bitcoin"
29     "blackbox"
30     "borgmatic"
31     "buildkite-agent"
32     "collectd"
33     "deluge"
34     "dmarc"
35     "dnsmasq"
36     "dnssec"
37     "domain"
38     "dovecot"
39     "fastly"
40     "flow"
41     "fritz"
42     "fritzbox"
43     "graphite"
44     "idrac"
45     "imap-mailstat"
46     "influxdb"
47     "ipmi"
48     "jitsi"
49     "json"
50     "junos-czerwonk"
51     "kea"
52     "keylight"
53     "knot"
54     "lnd"
55     "mail"
56     "mikrotik"
57     "modemmanager"
58     "mongodb"
59     "mysqld"
60     "nats"
61     "nextcloud"
62     "nginx"
63     "nginxlog"
64     "node"
65     "nut"
66     "pgbouncer"
67     "php-fpm"
68     "pihole"
69     "ping"
70     "postfix"
71     "postgres"
72     "process"
73     "pve"
74     "py-air-control"
75     "redis"
76     "restic"
77     "rspamd"
78     "rtl_433"
79     "sabnzbd"
80     "scaphandre"
81     "script"
82     "shelly"
83     "smartctl"
84     "smokeping"
85     "snmp"
86     "sql"
87     "statsd"
88     "surfboard"
89     "systemd"
90     "tor"
91     "unbound"
92     "unifi"
93     "unpoller"
94     "v2ray"
95     "varnish"
96     "wireguard"
97     "zfs"
98   ]
99     (name:
100       import (./. + "/exporters/${name}.nix") { inherit config lib pkgs options utils; }
101     )) // (mapAttrs
102     (name: params:
103       import (./. + "/exporters/${params.name}.nix") { inherit config lib pkgs options utils; type = params.type ; })
104     {
105       exportarr-bazarr = {
106         name = "exportarr";
107         type = "bazarr";
108       };
109       exportarr-lidarr = {
110         name = "exportarr";
111         type = "lidarr";
112       };
113       exportarr-prowlarr = {
114         name = "exportarr";
115         type = "prowlarr";
116       };
117       exportarr-radarr = {
118         name = "exportarr";
119         type = "radarr";
120       };
121       exportarr-readarr = {
122         name = "exportarr";
123         type = "readarr";
124       };
125       exportarr-sonarr = {
126         name = "exportarr";
127         type = "sonarr";
128       };
129     }
130   );
132   mkExporterOpts = ({ name, port }: {
133     enable = mkEnableOption "the prometheus ${name} exporter";
134     port = mkOption {
135       type = types.port;
136       default = port;
137       description = ''
138         Port to listen on.
139       '';
140     };
141     listenAddress = mkOption {
142       type = types.str;
143       default = "0.0.0.0";
144       description = ''
145         Address to listen on.
146       '';
147     };
148     extraFlags = mkOption {
149       type = types.listOf types.str;
150       default = [];
151       description = ''
152         Extra commandline options to pass to the ${name} exporter.
153       '';
154     };
155     openFirewall = mkOption {
156       type = types.bool;
157       default = false;
158       description = ''
159         Open port in firewall for incoming connections.
160       '';
161     };
162     firewallFilter = mkOption {
163       type = types.nullOr types.str;
164       default = null;
165       example = literalExpression ''
166         "-i eth0 -p tcp -m tcp --dport ${toString port}"
167       '';
168       description = ''
169         Specify a filter for iptables to use when
170         {option}`services.prometheus.exporters.${name}.openFirewall`
171         is true. It is used as `ip46tables -I nixos-fw firewallFilter -j nixos-fw-accept`.
172       '';
173     };
174     firewallRules = mkOption {
175       type = types.nullOr types.lines;
176       default = null;
177       example = literalExpression ''
178         iifname "eth0" tcp dport ${toString port} counter accept
179       '';
180       description = ''
181         Specify rules for nftables to add to the input chain
182         when {option}`services.prometheus.exporters.${name}.openFirewall` is true.
183       '';
184     };
185     user = mkOption {
186       type = types.str;
187       default = "${name}-exporter";
188       description = ''
189         User name under which the ${name} exporter shall be run.
190       '';
191     };
192     group = mkOption {
193       type = types.str;
194       default = "${name}-exporter";
195       description = ''
196         Group under which the ${name} exporter shall be run.
197       '';
198     };
199   });
201   mkSubModule = { name, port, extraOpts, imports }: {
202     ${name} = mkOption {
203       type = types.submodule [{
204         inherit imports;
205         options = (mkExporterOpts {
206           inherit name port;
207         } // extraOpts);
208       } ({ config, ... }: mkIf config.openFirewall {
209         firewallFilter = mkDefault "-p tcp -m tcp --dport ${toString config.port}";
210         firewallRules = mkDefault ''tcp dport ${toString config.port} accept comment "${name}-exporter"'';
211       })];
212       internal = true;
213       default = {};
214     };
215   };
217   mkSubModules = (foldl' (a: b: a//b) {}
218     (mapAttrsToList (name: opts: mkSubModule {
219       inherit name;
220       inherit (opts) port;
221       extraOpts = opts.extraOpts or {};
222       imports = opts.imports or [];
223     }) exporterOpts)
224   );
226   mkExporterConf = { name, conf, serviceOpts }:
227     let
228       enableDynamicUser = serviceOpts.serviceConfig.DynamicUser or true;
229       nftables = config.networking.nftables.enable;
230     in
231     mkIf conf.enable {
232       warnings = conf.warnings or [];
233       assertions = conf.assertions or [];
234       users.users."${name}-exporter" = (mkIf (conf.user == "${name}-exporter" && !enableDynamicUser) {
235         description = "Prometheus ${name} exporter service user";
236         isSystemUser = true;
237         inherit (conf) group;
238       });
239       users.groups = mkMerge [
240         (mkIf (conf.group == "${name}-exporter" && !enableDynamicUser) {
241           "${name}-exporter" = {};
242         })
243         (mkIf (name == "smartctl") {
244           "smartctl-exporter-access" = {};
245         })
246       ];
247       services.udev.extraRules = mkIf (name == "smartctl") ''
248         ACTION=="add", SUBSYSTEM=="nvme", KERNEL=="nvme[0-9]*", RUN+="${pkgs.acl}/bin/setfacl -m g:smartctl-exporter-access:rw /dev/$kernel"
249       '';
250       networking.firewall.extraCommands = mkIf (conf.openFirewall && !nftables) (concatStrings [
251         "ip46tables -A nixos-fw ${conf.firewallFilter} "
252         "-m comment --comment ${name}-exporter -j nixos-fw-accept"
253       ]);
254       networking.firewall.extraInputRules = mkIf (conf.openFirewall && nftables) conf.firewallRules;
255       systemd.services."prometheus-${name}-exporter" = mkMerge ([{
256         wantedBy = [ "multi-user.target" ];
257         after = [ "network.target" ];
258         serviceConfig.Restart = mkDefault "always";
259         serviceConfig.PrivateTmp = mkDefault true;
260         serviceConfig.WorkingDirectory = mkDefault /tmp;
261         serviceConfig.DynamicUser = mkDefault enableDynamicUser;
262         serviceConfig.User = mkDefault conf.user;
263         serviceConfig.Group = conf.group;
264         # Hardening
265         serviceConfig.CapabilityBoundingSet = mkDefault [ "" ];
266         serviceConfig.DeviceAllow = [ "" ];
267         serviceConfig.LockPersonality = true;
268         serviceConfig.MemoryDenyWriteExecute = true;
269         serviceConfig.NoNewPrivileges = true;
270         serviceConfig.PrivateDevices = mkDefault true;
271         serviceConfig.ProtectClock = mkDefault true;
272         serviceConfig.ProtectControlGroups = true;
273         serviceConfig.ProtectHome = true;
274         serviceConfig.ProtectHostname = true;
275         serviceConfig.ProtectKernelLogs = true;
276         serviceConfig.ProtectKernelModules = true;
277         serviceConfig.ProtectKernelTunables = true;
278         serviceConfig.ProtectSystem = mkDefault "strict";
279         serviceConfig.RemoveIPC = true;
280         serviceConfig.RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
281         serviceConfig.RestrictNamespaces = true;
282         serviceConfig.RestrictRealtime = true;
283         serviceConfig.RestrictSUIDSGID = true;
284         serviceConfig.SystemCallArchitectures = "native";
285         serviceConfig.UMask = "0077";
286       } serviceOpts ]);
287   };
291   options.services.prometheus.exporters = mkOption {
292     type = types.submodule {
293       options = (mkSubModules);
294       imports = [
295         ../../../misc/assertions.nix
296         (lib.mkRenamedOptionModule [ "unifi-poller" ] [ "unpoller" ])
297         (lib.mkRemovedOptionModule [ "minio" ] ''
298           The Minio exporter has been removed, as it was broken and unmaintained.
299           See the 24.11 release notes for more information.
300         '')
301       ];
302     };
303     description = "Prometheus exporter configuration";
304     default = {};
305     example = literalExpression ''
306       {
307         node = {
308           enable = true;
309           enabledCollectors = [ "systemd" ];
310         };
311         varnish.enable = true;
312       }
313     '';
314   };
316   config = mkMerge ([{
317     assertions = [ {
318       assertion = cfg.ipmi.enable -> (cfg.ipmi.configFile != null) -> (
319         !(lib.hasPrefix "/tmp/" cfg.ipmi.configFile)
320       );
321       message = ''
322         Config file specified in `services.prometheus.exporters.ipmi.configFile' must
323           not reside within /tmp - it won't be visible to the systemd service.
324       '';
325     } {
326       assertion = cfg.ipmi.enable -> (cfg.ipmi.webConfigFile != null) -> (
327         !(lib.hasPrefix "/tmp/" cfg.ipmi.webConfigFile)
328       );
329       message = ''
330         Config file specified in `services.prometheus.exporters.ipmi.webConfigFile' must
331           not reside within /tmp - it won't be visible to the systemd service.
332       '';
333     } {
334       assertion = cfg.snmp.enable -> (
335         (cfg.snmp.configurationPath == null) != (cfg.snmp.configuration == null)
336       );
337       message = ''
338         Please ensure you have either `services.prometheus.exporters.snmp.configuration'
339           or `services.prometheus.exporters.snmp.configurationPath' set!
340       '';
341     } {
342       assertion = cfg.mikrotik.enable -> (
343         (cfg.mikrotik.configFile == null) != (cfg.mikrotik.configuration == null)
344       );
345       message = ''
346         Please specify either `services.prometheus.exporters.mikrotik.configuration'
347           or `services.prometheus.exporters.mikrotik.configFile'.
348       '';
349     } {
350       assertion = cfg.mail.enable -> (
351         (cfg.mail.configFile == null) != (cfg.mail.configuration == null)
352       );
353       message = ''
354         Please specify either 'services.prometheus.exporters.mail.configuration'
355           or 'services.prometheus.exporters.mail.configFile'.
356       '';
357     } {
358       assertion = cfg.mysqld.runAsLocalSuperUser -> config.services.mysql.enable;
359       message = ''
360         The exporter is configured to run as 'services.mysql.user', but
361           'services.mysql.enable' is set to false.
362       '';
363     } {
364       assertion = cfg.nextcloud.enable -> (
365         (cfg.nextcloud.passwordFile == null) != (cfg.nextcloud.tokenFile == null)
366       );
367       message = ''
368         Please specify either 'services.prometheus.exporters.nextcloud.passwordFile' or
369           'services.prometheus.exporters.nextcloud.tokenFile'
370       '';
371     } {
372       assertion = cfg.sql.enable -> (
373         (cfg.sql.configFile == null) != (cfg.sql.configuration == null)
374       );
375       message = ''
376         Please specify either 'services.prometheus.exporters.sql.configuration' or
377           'services.prometheus.exporters.sql.configFile'
378       '';
379     } {
380       assertion = cfg.scaphandre.enable -> (pkgs.stdenv.targetPlatform.isx86_64 == true);
381       message = ''
382         Scaphandre only support x86_64 architectures.
383       '';
384     } {
385       assertion = cfg.scaphandre.enable -> ((lib.kernel.whenHelpers pkgs.linux.version).whenOlder "5.11" true).condition == false;
386       message = ''
387         Scaphandre requires a kernel version newer than '5.11', '${pkgs.linux.version}' given.
388       '';
389     } {
390       assertion = cfg.scaphandre.enable -> (builtins.elem "intel_rapl_common" config.boot.kernelModules);
391       message = ''
392         Scaphandre needs 'intel_rapl_common' kernel module to be enabled. Please add it in 'boot.kernelModules'.
393       '';
394     } {
395       assertion = cfg.idrac.enable -> (
396         (cfg.idrac.configurationPath == null) != (cfg.idrac.configuration == null)
397       );
398       message = ''
399         Please ensure you have either `services.prometheus.exporters.idrac.configuration'
400           or `services.prometheus.exporters.idrac.configurationPath' set!
401       '';
402     } {
403       assertion = cfg.deluge.enable -> (
404         (cfg.deluge.delugePassword == null) != (cfg.deluge.delugePasswordFile == null)
405       );
406       message = ''
407         Please ensure you have either `services.prometheus.exporters.deluge.delugePassword'
408           or `services.prometheus.exporters.deluge.delugePasswordFile' set!
409       '';
410     } {
411       assertion = cfg.pgbouncer.enable -> (
412         xor (cfg.pgbouncer.connectionEnvFile == null) (cfg.pgbouncer.connectionString == null)
413       );
414       message = ''
415         Options `services.prometheus.exporters.pgbouncer.connectionEnvFile` and
416         `services.prometheus.exporters.pgbouncer.connectionString` are mutually exclusive!
417       '';
418     }] ++ (flip map (attrNames exporterOpts) (exporter: {
419       assertion = cfg.${exporter}.firewallFilter != null -> cfg.${exporter}.openFirewall;
420       message = ''
421         The `firewallFilter'-option of exporter ${exporter} doesn't have any effect unless
422         `openFirewall' is set to `true'!
423       '';
424     })) ++ config.services.prometheus.exporters.assertions;
425     warnings = [
426       (mkIf (config.services.prometheus.exporters.idrac.enable && config.services.prometheus.exporters.idrac.configurationPath != null) ''
427           Configuration file in `services.prometheus.exporters.idrac.configurationPath` may override
428           `services.prometheus.exporters.idrac.listenAddress` and/or `services.prometheus.exporters.idrac.port`.
429           Consider using `services.prometheus.exporters.idrac.configuration` instead.
430         ''
431       )
432     ] ++ config.services.prometheus.exporters.warnings;
433   }]  ++ [(mkIf config.services.prometheus.exporters.rtl_433.enable {
434     hardware.rtl-sdr.enable = mkDefault true;
435   })] ++ [(mkIf config.services.postfix.enable {
436     services.prometheus.exporters.postfix.group = mkDefault config.services.postfix.setgidGroup;
437   })] ++ [(mkIf config.services.prometheus.exporters.deluge.enable {
438     system.activationScripts = {
439       deluge-exported.text = ''
440       mkdir -p /etc/deluge-exporter
441       echo "DELUGE_PASSWORD=$(cat ${config.services.prometheus.exporters.deluge.delugePasswordFile})" > /etc/deluge-exporter/password
442       '';
443     };
444   })] ++ (mapAttrsToList (name: conf:
445     mkExporterConf {
446       inherit name;
447       inherit (conf) serviceOpts;
448       conf = cfg.${name};
449     }) exporterOpts)
450   );
452   meta = {
453     doc = ./exporters.md;
454     maintainers = [ maintainers.willibutz ];
455   };