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
This commit is contained in:
Thorben Günther 2023-08-25 14:57:54 +02:00
parent 1bfb814f14
commit 25e65db8bd
No known key found for this signature in database
GPG key ID: 415CD778D8C5AFED
4 changed files with 51 additions and 7 deletions

View file

@ -51,6 +51,9 @@ ntfy {
# ntfy authentication via access tokens (https://docs.ntfy.sh/publish/#access-tokens) # 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. # Either access-token or a user/password combination can be used - not both.
access-token foobar 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. # Forward all messages to the specified email address.
email-address foo@bar.com email-address foo@bar.com
# Call the specified number for all alerts. Use `yes` to pick the first of your verified numbers. # Call the specified number for all alerts. Use `yes` to pick the first of your verified numbers.

View file

@ -40,6 +40,7 @@ type ntfyConfig struct {
User string User string
Password string Password string
AccessToken string AccessToken string
CertFingerprint string
EmailAddress string EmailAddress string
Call string Call string
} }
@ -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") 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") d = ntfyDir.Children.Get("email-address")
if d != nil { if d != nil {
if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil { if err := d.ParseParams(&config.Ntfy.EmailAddress); err != nil {

View file

@ -45,6 +45,7 @@ resolved {
ntfy { ntfy {
topic https://ntfy.sh/alertmanager-alerts topic https://ntfy.sh/alertmanager-alerts
certificate-fingerprint 136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299
user user user user
password pass password pass
} }
@ -71,7 +72,12 @@ cache {
AlertMode: Multi, AlertMode: Multi,
User: "webhookUser", User: "webhookUser",
Password: "webhookPass", 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"}, Labels: labels{Order: []string{"severity", "instance"},
Label: map[string]labelConfig{ Label: map[string]labelConfig{
"severity:critical": { "severity:critical": {

27
main.go
View file

@ -5,9 +5,13 @@ import (
"context" "context"
"crypto/sha512" "crypto/sha512"
"crypto/subtle" "crypto/subtle"
"crypto/tls"
"crypto/x509"
_ "embed" _ "embed"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"errors"
"flag" "flag"
"fmt" "fmt"
"log/slog" "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)) 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) resp, err := br.client.Do(req)
if err != nil { if err != nil {
return err return err