···113113114114- [tuxedo-rs](https://github.com/AaronErhardt/tuxedo-rs), Rust utilities for interacting with hardware from TUXEDO Computers.
115115116116+- [certspotter](https://github.com/SSLMate/certspotter), a certificate transparency log monitor. Available as [services.certspotter](#opt-services.certspotter.enable).
117117+116118- [audiobookshelf](https://github.com/advplyr/audiobookshelf/), a self-hosted audiobook and podcast server. Available as [services.audiobookshelf](#opt-services.audiobookshelf.enable).
117119118120- [ZITADEL](https://zitadel.com), a turnkey identity and access management platform. Available as [services.zitadel](#opt-services.zitadel.enable).
···11+# Cert Spotter {#module-services-certspotter}
22+33+Cert Spotter is a tool for monitoring [Certificate Transparency](https://en.wikipedia.org/wiki/Certificate_Transparency)
44+logs.
55+66+## Service Configuration {#modules-services-certspotter-service-configuration}
77+88+A basic config that notifies you of all certificate changes for your
99+domain would look as follows:
1010+1111+```nix
1212+services.certspotter = {
1313+ enable = true;
1414+ # replace example.org with your domain name
1515+ watchlist = [ ".example.org" ];
1616+ emailRecipients = [ "webmaster@example.org" ];
1717+};
1818+1919+# Configure an SMTP client
2020+programs.msmtp.enable = true;
2121+# Or you can use any other module that provides sendmail, like
2222+# services.nullmailer, services.opensmtpd, services.postfix
2323+```
2424+2525+In this case, the leading dot in `".example.org"` means that Cert
2626+Spotter should monitor not only `example.org`, but also all of its
2727+subdomains.
2828+2929+## Operation {#modules-services-certspotter-operation}
3030+3131+**By default, NixOS configures Cert Spotter to skip all certificates
3232+issued before its first launch**, because checking the entire
3333+Certificate Transparency logs requires downloading tens of terabytes of
3434+data. If you want to check the *entire* logs for previously issued
3535+certificates, you have to set `services.certspotter.startAtEnd` to
3636+`false` and remove all previously saved log state in
3737+`/var/lib/certspotter/logs`. The downloaded logs aren't saved, so if you
3838+add a new domain to the watchlist and want Cert Spotter to go through
3939+the logs again, you will have to remove `/var/lib/certspotter/logs`
4040+again.
4141+4242+After catching up with the logs, Cert Spotter will start monitoring live
4343+logs. As of October 2023, it uses around **20 Mbps** of traffic on
4444+average.
4545+4646+## Hooks {#modules-services-certspotter-hooks}
4747+4848+Cert Spotter supports running custom hooks instead of (or in addition
4949+to) sending emails. Hooks are shell scripts that will be passed certain
5050+environment variables.
5151+5252+To see hook documentation, see Cert Spotter's man pages:
5353+5454+```ShellSession
5555+nix-shell -p certspotter --run 'man 8 certspotter-script'
5656+```
5757+5858+For example, you can remove `emailRecipients` and send email
5959+notifications manually using the following hook:
6060+6161+```nix
6262+services.certspotter.hooks = [
6363+ (pkgs.writeShellScript "certspotter-hook" ''
6464+ function print_email() {
6565+ echo "Subject: [certspotter] $SUMMARY"
6666+ echo "Mime-Version: 1.0"
6767+ echo "Content-Type: text/plain; charset=US-ASCII"
6868+ echo
6969+ cat "$TEXT_FILENAME"
7070+ }
7171+ print_email | ${config.services.certspotter.sendmailPath} -i webmaster@example.org
7272+ '')
7373+];
7474+```
+143
nixos/modules/services/monitoring/certspotter.nix
···11+{ config
22+, lib
33+, pkgs
44+, ... }:
55+66+let
77+ cfg = config.services.certspotter;
88+99+ configDir = pkgs.linkFarm "certspotter-config" (
1010+ lib.toList {
1111+ name = "watchlist";
1212+ path = pkgs.writeText "certspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
1313+ }
1414+ ++ lib.optional (cfg.emailRecipients != [ ]) {
1515+ name = "email_recipients";
1616+ path = pkgs.writeText "certspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
1717+ }
1818+ # always generate hooks dir when no emails are provided to allow running cert spotter with no hooks/emails
1919+ ++ lib.optional (cfg.emailRecipients == [ ] || cfg.hooks != [ ]) {
2020+ name = "hooks.d";
2121+ path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
2222+ inherit path;
2323+ name = "hook${toString i}";
2424+ }) cfg.hooks);
2525+ });
2626+in
2727+{
2828+ options.services.certspotter = {
2929+ enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";
3030+3131+ package = lib.mkPackageOptionMD pkgs "certspotter" { };
3232+3333+ startAtEnd = lib.mkOption {
3434+ type = lib.types.bool;
3535+ description = ''
3636+ Whether to skip certificates issued before the first launch of Cert Spotter.
3737+ Setting this to `false` will cause Cert Spotter to download tens of terabytes of data.
3838+ '';
3939+ default = true;
4040+ };
4141+4242+ sendmailPath = lib.mkOption {
4343+ type = with lib.types; nullOr path;
4444+ description = ''
4545+ Path to the `sendmail` binary. By default, the local sendmail wrapper is used
4646+ (see {option}`services.mail.sendmailSetuidWrapper`}).
4747+ '';
4848+ example = lib.literalExpression ''"''${pkgs.system-sendmail}/bin/sendmail"'';
4949+ };
5050+5151+ watchlist = lib.mkOption {
5252+ type = with lib.types; listOf str;
5353+ description = "Domain names to watch. To monitor a domain with all subdomains, prefix its name with `.` (e.g. `.example.org`).";
5454+ default = [ ];
5555+ example = [ ".example.org" "another.example.com" ];
5656+ };
5757+5858+ emailRecipients = lib.mkOption {
5959+ type = with lib.types; listOf str;
6060+ description = "A list of email addresses to send certificate updates to.";
6161+ default = [ ];
6262+ };
6363+6464+ hooks = lib.mkOption {
6565+ type = with lib.types; listOf path;
6666+ description = ''
6767+ Scripts to run upon the detection of a new certificate. See `man 8 certspotter-script` or
6868+ [the GitHub page](https://github.com/SSLMate/certspotter/blob/${pkgs.certspotter.src.rev or "master"}/man/certspotter-script.md)
6969+ for more info.
7070+ '';
7171+ default = [ ];
7272+ example = lib.literalExpression ''
7373+ [
7474+ (pkgs.writeShellScript "certspotter-hook" '''
7575+ echo "Event summary: $SUMMARY."
7676+ ''')
7777+ ]
7878+ '';
7979+ };
8080+8181+ extraFlags = lib.mkOption {
8282+ type = with lib.types; listOf str;
8383+ description = "Extra command-line arguments to pass to Cert Spotter";
8484+ example = [ "-no_save" ];
8585+ default = [ ];
8686+ };
8787+ };
8888+8989+ config = lib.mkIf cfg.enable {
9090+ assertions = [
9191+ {
9292+ assertion = (cfg.emailRecipients != [ ]) -> (cfg.sendmailPath != null);
9393+ message = ''
9494+ You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
9595+ or services.certspotter.sendmailPath
9696+ '';
9797+ }
9898+ ];
9999+100100+ services.certspotter.sendmailPath = let
101101+ inherit (config.security) wrapperDir;
102102+ inherit (config.services.mail) sendmailSetuidWrapper;
103103+ in lib.mkMerge [
104104+ (lib.mkIf (sendmailSetuidWrapper != null) (lib.mkOptionDefault "${wrapperDir}/${sendmailSetuidWrapper.program}"))
105105+ (lib.mkIf (sendmailSetuidWrapper == null) (lib.mkOptionDefault null))
106106+ ];
107107+108108+ users.users.certspotter = {
109109+ description = "Cert Spotter user";
110110+ group = "certspotter";
111111+ home = "/var/lib/certspotter";
112112+ isSystemUser = true;
113113+ };
114114+ users.groups.certspotter = { };
115115+116116+ systemd.services.certspotter = {
117117+ description = "Cert Spotter - Certificate Transparency Monitor";
118118+ after = [ "network.target" ];
119119+ wantedBy = [ "multi-user.target" ];
120120+ environment.CERTSPOTTER_CONFIG_DIR = configDir;
121121+ environment.SENDMAIL_PATH = if cfg.sendmailPath != null then cfg.sendmailPath else "/run/current-system/sw/bin/false";
122122+ script = ''
123123+ export CERTSPOTTER_STATE_DIR="$STATE_DIRECTORY"
124124+ cd "$CERTSPOTTER_STATE_DIR"
125125+ ${lib.optionalString cfg.startAtEnd ''
126126+ if [[ ! -d logs ]]; then
127127+ # Don't download certificates issued before the first launch
128128+ exec ${cfg.package}/bin/certspotter -start_at_end ${lib.escapeShellArgs cfg.extraFlags}
129129+ fi
130130+ ''}
131131+ exec ${cfg.package}/bin/certspotter ${lib.escapeShellArgs cfg.extraFlags}
132132+ '';
133133+ serviceConfig = {
134134+ User = "certspotter";
135135+ Group = "certspotter";
136136+ StateDirectory = "certspotter";
137137+ };
138138+ };
139139+ };
140140+141141+ meta.maintainers = with lib.maintainers; [ chayleaf ];
142142+ meta.doc = ./certspotter.md;
143143+}