server: add certspotter

This commit is contained in:
chayleaf 2023-10-24 00:19:12 +07:00
parent eda0322bc7
commit 67f43298e8
6 changed files with 245 additions and 0 deletions

View file

@ -156,6 +156,7 @@
./system/devices/radxa-rock5a-server.nix ./system/devices/radxa-rock5a-server.nix
(if devMaubot then import /${devPath}/maubot.nix/module else maubot.nixosModules.default) (if devMaubot then import /${devPath}/maubot.nix/module else maubot.nixosModules.default)
./system/modules/scanservjs.nix ./system/modules/scanservjs.nix
./system/modules/certspotter.nix
]; ];
}; };
server-cross = crossConfig server; server-cross = crossConfig server;

View file

@ -0,0 +1,71 @@
diff --git a/cmd/certspotter/main.go b/cmd/certspotter/main.go
index 9730789..f2eb081 100644
--- a/cmd/certspotter/main.go
+++ b/cmd/certspotter/main.go
@@ -163,6 +163,7 @@ func main() {
logs string
noSave bool
script string
+ sendmail string
startAtEnd bool
stateDir string
stdout bool
@@ -176,6 +177,7 @@ func main() {
flag.StringVar(&flags.logs, "logs", defaultLogList, "File path or URL of JSON list of logs to monitor")
flag.BoolVar(&flags.noSave, "no_save", false, "Do not save a copy of matching certificates in state directory")
flag.StringVar(&flags.script, "script", "", "Program to execute when a matching certificate is discovered")
+ flag.StringVar(&flags.sendmail, "sendmail", "/usr/sbin/sendmail", "Path to the sendmail-compatible program to use")
flag.BoolVar(&flags.startAtEnd, "start_at_end", false, "Start monitoring logs from the end rather than the beginning (saves considerable bandwidth)")
flag.StringVar(&flags.stateDir, "state_dir", defaultStateDir(), "Directory for storing log position and discovered certificates")
flag.BoolVar(&flags.stdout, "stdout", false, "Write matching certificates to stdout")
@@ -201,6 +203,7 @@ func main() {
Verbose: flags.verbose,
Script: flags.script,
ScriptDir: defaultScriptDir(),
+ SendmailPath: flags.sendmail,
Email: flags.email,
Stdout: flags.stdout,
HealthCheckInterval: flags.healthcheck,
diff --git a/monitor/config.go b/monitor/config.go
index 1e0d60c..d1bc430 100644
--- a/monitor/config.go
+++ b/monitor/config.go
@@ -20,6 +20,7 @@ type Config struct {
WatchList WatchList
Verbose bool
SaveCerts bool
+ SendmailPath string
Script string
ScriptDir string
Email []string
diff --git a/monitor/notify.go b/monitor/notify.go
index 8fc6d09..86cabca 100644
--- a/monitor/notify.go
+++ b/monitor/notify.go
@@ -36,7 +36,7 @@ func notify(ctx context.Context, config *Config, notif notification) error {
}
if len(config.Email) > 0 {
- if err := sendEmail(ctx, config.Email, notif); err != nil {
+ if err := sendEmail(ctx, config.SendmailPath, config.Email, notif); err != nil {
return err
}
}
@@ -62,7 +62,7 @@ func writeToStdout(notif notification) {
os.Stdout.WriteString(notif.Text() + "\n")
}
-func sendEmail(ctx context.Context, to []string, notif notification) error {
+func sendEmail(ctx context.Context, sendmailPath string, to []string, notif notification) error {
stdin := new(bytes.Buffer)
stderr := new(bytes.Buffer)
@@ -77,7 +77,7 @@ func sendEmail(ctx context.Context, to []string, notif notification) error {
args := []string{"-i", "--"}
args = append(args, to...)
- sendmail := exec.CommandContext(ctx, "/usr/sbin/sendmail", args...)
+ sendmail := exec.CommandContext(ctx, sendmailPath, args...)
sendmail.Stdin = stdin
sendmail.Stderr = stderr

View file

@ -0,0 +1,41 @@
{ lib
, buildGoModule
, fetchFromGitHub
, lowdown
}:
buildGoModule rec {
pname = "certspotter";
version = "0.16.0";
src = fetchFromGitHub {
owner = "SSLMate";
repo = "certspotter";
rev = "v${version}";
hash = "sha256-0+7GWxbV4j2vVdmool8J9hqRqUi8O/yKedCyynWJDkE=";
};
vendorHash = "sha256-haYmWc2FWZNFwMhmSy3DAtj9oW5G82dX0fxpGqI8Hbw=";
patches = [ ./configurable-sendmail.patch ];
ldflags = [ "-s" "-w" ];
nativeBuildInputs = [ lowdown ];
postInstall = ''
cd man
make
mkdir -p $out/share/man/man8
mv *.8 $out/share/man/man8
'';
meta = with lib; {
description = "Certificate Transparency Log Monitor";
homepage = "https://github.com/SSLMate/certspotter";
changelog = "https://github.com/SSLMate/certspotter/blob/${src.rev}/CHANGELOG.md";
license = licenses.mpl20;
mainProgram = "certspotter";
maintainers = with maintainers; [ chayleaf ];
};
}

View file

@ -60,6 +60,7 @@ in
meta = builtins.removeAttrs old.meta [ "broken" ]; meta = builtins.removeAttrs old.meta [ "broken" ];
}); });
certspotter = callPackage ./certspotter { };
clang-tools_latest = pkgs.clang-tools_16; clang-tools_latest = pkgs.clang-tools_16;
clang_latest = pkgs.clang_16; clang_latest = pkgs.clang_16;
/*ghidra = pkgs.ghidra.overrideAttrs (old: { /*ghidra = pkgs.ghidra.overrideAttrs (old: {

View file

@ -349,6 +349,25 @@ in {
}; };
}; };
users.users.certspotter.extraGroups = [ "acme" ];
services.certspotter = {
enable = true;
watchlist = [ ".pavluk.org" ];
hooks = let
openssl = "${pkgs.openssl.bin}/bin/openssl";
in lib.toList (pkgs.writeShellScript "certspotter-hook" ''
if [[ "$EVENT" == discovered_cert ]]; then
mkdir -p /var/lib/certspotter/allowed_tbs
for cert in $(find /var/lib/acme -regex ".*/fullchain.pem"); do
hash="$(${openssl} x509 -in "$cert" -pubkey -noout | ${openssl} pkey -pubin -outform DER | ${openssl} sha256 | cut -d" " -f2)"
touch "/var/lib/certspotter/allowed_tbs/$hash"
done
[[ -f "/var/lib/certspotter/allowed_tbs/$TBS_SHA256" ]] && exit 0
fi
(echo "Subject: $SUMMARY" && echo && cat "$TEXT_FILENAME") | /run/wrappers/bin/sendmail -i webmaster-certspotter@${cfg.domainName}
'');
};
/*locations."/dns-query".extraConfig = '' /*locations."/dns-query".extraConfig = ''
grpc_pass grpc://127.0.0.1:53453; grpc_pass grpc://127.0.0.1:53453;
'';*/ '';*/

View file

@ -0,0 +1,112 @@
{ config
, lib
, pkgs
, ... }:
let
cfg = config.services.certspotter;
in {
options.services.certspotter = {
enable = lib.mkEnableOption "Cert Spotter, a Certificate Transparency log monitor";
sendmailPath = lib.mkOption {
type = lib.types.path;
description = ''
Path to the `sendmail` binary. By default, the local sendmail wrapper is used
(see `config.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/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 = [ "-start_at_end" ];
default = [ ];
};
};
config = lib.mkIf cfg.enable {
assertions = [
{
assertion = cfg.watchlist != [ ];
message = "You must specify at least one domain for Cert Spotter to watch";
}
{
assertion = cfg.hooks != [] || cfg.emailRecipients != [];
message = "You must specify at least one hook or email recipient for Cert Spotter";
}
{
assertion = (cfg.emailRecipients != []) -> (cfg.sendmailPath != "/run/current-system/sw/bin/false");
message = ''
You must configure the sendmail setuid wrapper (services.mail.sendmailSetuidWrapper)
or services.certspotter.sendmailPath
'';
}
];
services.certspotter.sendmailPath = lib.mkMerge [
(lib.mkIf (config.services.mail.sendmailSetuidWrapper != null) (lib.mkOptionDefault "/run/wrappers/bin/sendmail"))
(lib.mkIf (config.services.mail.sendmailSetuidWrapper == null) (lib.mkOptionDefault "/run/current-system/sw/bin/false"))
];
users.users.certspotter = {
group = "certspotter";
home = "/var/lib/certspotter";
createHome = true;
isSystemUser = true;
# uid = config.ids.uids.certspotter;
};
users.groups.certspotter = {
# gid = config.ids.gids.certspotter;
};
systemd.services.certspotter = {
description = "Cert Spotter - Certificate Transparency Monitor";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment.CERTSPOTTER_CONFIG_DIR = pkgs.linkFarm "certspotter-config"
(lib.toList {
name = "watchlist";
path = pkgs.writeText "cerspotter-watchlist" (builtins.concatStringsSep "\n" cfg.watchlist);
}
++ lib.optional (cfg.emailRecipients != [ ]) {
name = "email_recipients";
path = pkgs.writeText "cerspotter-email_recipients" (builtins.concatStringsSep "\n" cfg.emailRecipients);
}
++ lib.optional (cfg.hooks != [ ]) {
name = "hooks.d";
path = pkgs.linkFarm "certspotter-hooks" (lib.imap1 (i: path: {
inherit path;
name = "hook${toString i}";
}) cfg.hooks);
});
environment.CERTSPOTTER_STATE_DIR = "/var/lib/certspotter";
serviceConfig = {
User = "certspotter";
Group = "certspotter";
WorkingDirectory = "/var/lib/certspotter";
ExecStart = "${pkgs.certspotter}/bin/certspotter -sendmail ${cfg.sendmailPath} ${lib.escapeShellArgs cfg.extraFlags}";
};
};
};
}