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