1{ config
2, lib
3, pkgs
4, ... }:
5
6let
7 cfg = config.services.certspotter;
8
9 configDir = pkgs.linkFarm "certspotter-config" (
10 lib.toList {
11 name = "watchlist";
12 path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
13 }
14 ++ lib.optional (cfg.emailRecipients != [ ]) {
15 name = "email_recipients";
16 path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
17 }
18 # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails
19 ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) {
20 name = "hooks.d";
21 path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
22 inherit path;
23 name = "hook${toString i}";
24 }) cfg.hooks);
25 });
26in
27{
28 options.services.certspotter = {
29 enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";
30
31 package = lib.mkPackageOptionMD pkgs "certspotter" { };
32
33 startAtEnd = lib.mkOption {
34 type = lib.types.bool;
35 description = ''
36 Whether to skip certificates issued before the first launch of Cert Spotter.
37 Setting this to `false` will cause Cert Spotter to download tens of terabytes of data.
38 '';
39 default = true;
40 };
41
42 sendmailPath = lib.mkOption {
43 type = with lib.types; nullOr path;
44 description = ''
45 Path to the `sendmail` binary. By default, the local sendmail wrapper is used
46 (see {option}`services.mail.sendmailSetuidWrapper`}).
47 '';
48 example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"'';
49 };
50
51 watchlist = lib.mkOption {
52 type = with lib.types; listOf str;
53 description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`).";
54 default = [ ];
55 example = [ ".example.org" "another.example.com" ];
56 };
57
58 emailRecipients = lib.mkOption {
59 type = with lib.types; listOf str;
60 description = "A list of email addresses to send certificate updates to.";
61 default = [ ];
62 };
63
64 hooks = lib.mkOption {
65 type = with lib.types; listOf path;
66 description = ''
67 Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or
68 [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md)
69 for more info.
70 '';
71 default = [ ];
72 example = lib.literalExpression ''
73 [
74 (pkgs.writeShellScript "certspotter-hook" '''
75 echo "Event summary: $SUMMARY."
76 ''')
77 ]
78 '';
79 };
80
81 extraFlags = lib.mkOption {
82 type = with lib.types; listOf str;
83 description = "Extra command-line arguments to pass to Cert Spotter";
84 example = [ "-no_save" ];
85 default = [ ];
86 };
87 };
88
89 config = lib.mkIf cfg.enable {
90 assertions = [
91 {
92 assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null);
93 message = ''
94 You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
95 or services.certspotter.sendmailPath
96 '';
97 }
98 ];
99
100 services.certspotter.sendmailPath = let
101 inherit (config.security) wrapperDir;
102 inherit (config.services.mail) sendmailSetuidWrapper;
103 in lib.mkMerge [
104 (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}"))
105 (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null))
106 ];
107
108 users.users.certspotter = {
109 description = "Cert Spotter user";
110 group = "certspotter";
111 home = "/var/lib/certspotter";
112 isSystemUser = true;
113 };
114 users.groups.certspotter = { };
115
116 systemd.services.certspotter = {
117 description = "Cert Spotter - Certificate Transparency Monitor";
118 after = [ "network.target" ];
119 wantedBy = [ "multi-user.target" ];
120 environment.CERTSPOTTER_CONFIG_DIR = configDir;
121 environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false";
122 script = ''
123 export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY"
124 cd "$CERTSPOTTER_STATE_DIR"
125 ${lib.optionalString cfg.startAtEnd ''
126 if [[ ! -d logs ]]; then
127 # Don't download certificates issued before the first launch
128 exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags}
129 fi
130 ''}
131 exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags}
132 '';
133 serviceConfig = {
134 User = "certspotter";
135 Group = "certspotter";
136 StateDirectory = "certspotter";
137 };
138 };
139 };
140
141 meta.maintainers = with lib.maintainers; [ chayleaf ];
142 meta.doc = ./certspotter.md;
143}