about summary refs log tree commit diff
path: root/nixos/modules/services/monitoring/certspotter.nix
blob: 5551f0e37c512ee077805edd6d34fd13e7c4ee87 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
{ config
, lib
, pkgs
, ... }:

let
  cfg = config.services.certspotter;

  configDir = pkgs.linkFarm "certspotter-config" (
    lib.toList {
      name = "watchlist";
      path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
    }
    ++ lib.optional (cfg.emailRecipients != [ ]) {
      name = "email_recipients";
      path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
    }
    # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails
    ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) {
      name = "hooks.d";
      path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
        inherit path;
        name = "hook${toString i}";
      }) cfg.hooks);
    });
in
{
  options.services.certspotter = {
    enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";

    package = lib.mkPackageOption pkgs "certspotter" { };

    startAtEnd = lib.mkOption {
      type = lib.types.bool;
      description = ''
        Whether to skip certificates issued before the first launch of Cert Spotter.
        Setting this to `false` will cause Cert Spotter to download tens of terabytes of data.
      '';
      default = true;
    };

    sendmailPath = lib.mkOption {
      type = with lib.types; nullOr path;
      description = ''
        Path to the `sendmail` binary. By default, the local sendmail wrapper is used
        (see {option}`services.mail.sendmailSetuidWrapper`}).
      '';
      example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"'';
    };

    watchlist = lib.mkOption {
      type = with lib.types; listOf str;
      description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`).";
      default = [ ];
      example = [ ".example.org" "another.example.com" ];
    };

    emailRecipients = lib.mkOption {
      type = with lib.types; listOf str;
      description = "A list of email addresses to send certificate updates to.";
      default = [ ];
    };

    hooks = lib.mkOption {
      type = with lib.types; listOf path;
      description = ''
        Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or
        [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md)
        for more info.
      '';
      default = [ ];
      example = lib.literalExpression ''
        [
          (pkgs.writeShellScript "certspotter-hook" '''
            echo "Event summary: $SUMMARY."
          ''')
        ]
      '';
    };

    extraFlags = lib.mkOption {
      type = with lib.types; listOf str;
      description = "Extra command-line arguments to pass to Cert Spotter";
      example = [ "-no_save" ];
      default = [ ];
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null);
        message = ''
          You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
          or services.certspotter.sendmailPath
        '';
      }
    ];

    services.certspotter.sendmailPath = let
      inherit (config.security) wrapperDir;
      inherit (config.services.mail) sendmailSetuidWrapper;
    in lib.mkMerge [
      (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}"))
      (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null))
    ];

    users.users.certspotter = {
      description = "Cert Spotter user";
      group = "certspotter";
      home = "/var/lib/certspotter";
      isSystemUser = true;
    };
    users.groups.certspotter = { };

    systemd.services.certspotter = {
      description = "Cert Spotter - Certificate Transparency Monitor";
      after = [ "network.target" ];
      wantedBy = [ "multi-user.target" ];
      environment.CERTSPOTTER_CONFIG_DIR = configDir;
      environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false";
      script = ''
        export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY"
        cd "$CERTSPOTTER_STATE_DIR"
        ${lib.optionalString cfg.startAtEnd ''
          if [[ ! -d logs ]]; then
            # Don't download certificates issued before the first launch
            exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags}
          fi
        ''}
        exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags}
      '';
      serviceConfig = {
        User = "certspotter";
        Group = "certspotter";
        StateDirectory = "certspotter";
      };
    };
  };

  meta.maintainers = with lib.maintainers; [ chayleaf ];
  meta.doc = ./certspotter.md;
}