server: add certspotter
This commit is contained in:
parent
eda0322bc7
commit
67f43298e8
|
@ -156,6 +156,7 @@
|
|||
./system/devices/radxa-rock5a-server.nix
|
||||
(if devMaubot then import /${devPath}/maubot.nix/module else maubot.nixosModules.default)
|
||||
./system/modules/scanservjs.nix
|
||||
./system/modules/certspotter.nix
|
||||
];
|
||||
};
|
||||
server-cross = crossConfig server;
|
||||
|
|
71
pkgs/certspotter/configurable-sendmail.patch
Normal file
71
pkgs/certspotter/configurable-sendmail.patch
Normal 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
|
||||
|
41
pkgs/certspotter/default.nix
Normal file
41
pkgs/certspotter/default.nix
Normal 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 ];
|
||||
};
|
||||
}
|
|
@ -60,6 +60,7 @@ in
|
|||
meta = builtins.removeAttrs old.meta [ "broken" ];
|
||||
});
|
||||
|
||||
certspotter = callPackage ./certspotter { };
|
||||
clang-tools_latest = pkgs.clang-tools_16;
|
||||
clang_latest = pkgs.clang_16;
|
||||
/*ghidra = pkgs.ghidra.overrideAttrs (old: {
|
||||
|
|
|
@ -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 = ''
|
||||
grpc_pass grpc://127.0.0.1:53453;
|
||||
'';*/
|
||||
|
|
112
system/modules/certspotter.nix
Normal file
112
system/modules/certspotter.nix
Normal 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}";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue