From 25e65db8bdd2de06aea5897a599ef746fadfa4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorben=20G=C3=BCnther?= Date: Fri, 25 Aug 2023 14:57:54 +0200 Subject: [PATCH] Add support for self signed ntfy certificates A fingerprint can be specified in the configuration file. If it matches the one from the server certificate, it will be accepted. Closes: https://todo.xenrox.net/~xenrox/ntfy-alertmanager/22 --- config.scfg | 3 +++ config/config.go | 20 ++++++++++++++------ config/config_test.go | 8 +++++++- main.go | 27 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 7 deletions(-) diff --git a/config.scfg b/config.scfg index e00271f..434256d 100644 --- a/config.scfg +++ b/config.scfg @@ -51,6 +51,9 @@ ntfy { # ntfy authentication via access tokens (https://docs.ntfy.sh/publish/#access-tokens) # Either access-token or a user/password combination can be used - not both. access-token foobar + # When using (self signed) certificates that cannot be verified, you can instead specify + # the SHA512 fingerprint. + certificate-fingerprint 136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299 # Forward all messages to the specified email address. email-address foo@bar.com # Call the specified number for all alerts. Use `yes` to pick the first of your verified numbers. diff --git a/config/config.go b/config/config.go index e8a2db8..b6c5bb2 100644 --- a/config/config.go +++ b/config/config.go @@ -36,12 +36,13 @@ type Config struct { } type ntfyConfig struct { - Topic string - User string - Password string - AccessToken string - EmailAddress string - Call string + Topic string + User string + Password string + AccessToken string + CertFingerprint string + EmailAddress string + Call string } type labels struct { @@ -277,6 +278,13 @@ func ReadConfig(path string) (*Config, error) { return nil, errors.New("ntfy: cannot use both an access-token and a user/password at the same time") } + d = ntfyDir.Children.Get("certificate-fingerprint") + if d != nil { + if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil { + return nil, err + } + } + d = ntfyDir.Children.Get("email-address") if d != nil { if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil { diff --git a/config/config_test.go b/config/config_test.go index 6795dba..3daf746 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -45,6 +45,7 @@ resolved { ntfy { topic https://ntfy.sh/alertmanager-alerts + certificate-fingerprint 136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299 user user password pass } @@ -71,7 +72,12 @@ cache { AlertMode: Multi, User: "webhookUser", Password: "webhookPass", - Ntfy: ntfyConfig{Topic: "https://ntfy.sh/alertmanager-alerts", User: "user", Password: "pass"}, + Ntfy: ntfyConfig{ + Topic: "https://ntfy.sh/alertmanager-alerts", + User: "user", + Password: "pass", + CertFingerprint: "136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299", + }, Labels: labels{Order: []string{"severity", "instance"}, Label: map[string]labelConfig{ "severity:critical": { diff --git a/main.go b/main.go index 47b7e0d..7fa4e99 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,13 @@ import ( "context" "crypto/sha512" "crypto/subtle" + "crypto/tls" + "crypto/x509" _ "embed" "encoding/base64" + "encoding/hex" "encoding/json" + "errors" "flag" "fmt" "log/slog" @@ -329,6 +333,29 @@ func (br *bridge) publish(n *notification) error { req.Header.Set("Actions", fmt.Sprintf("http, Silence, %s, method=POST, body=%s%s", url, n.silenceBody, authString)) } + configFingerprint := br.cfg.Ntfy.CertFingerprint + if configFingerprint != "" { + tlsCfg := &tls.Config{} + tlsCfg.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { + for _, rawCert := range rawCerts { + hash := sha512.Sum512(rawCert) + if hex.EncodeToString(hash[:]) == configFingerprint { + return nil + } + } + + if len(rawCerts) == 0 { + return errors.New("the ntfy server does not offer a certificate") + } + + hash := sha512.Sum512(rawCerts[0]) + return fmt.Errorf("ntfy certificate fingerprint does not match: expected %q, got %q", hex.EncodeToString(hash[:]), configFingerprint) + } + + tlsCfg.InsecureSkipVerify = true + br.client.Transport = &http.Transport{TLSClientConfig: tlsCfg} + } + resp, err := br.client.Do(req) if err != nil { return err