Compare commits
10 commits
25e65db8bd
...
e1ae4d2e43
Author | SHA1 | Date | |
---|---|---|---|
|
e1ae4d2e43 | ||
|
8868bfa20d | ||
|
9a030f2902 | ||
|
9b4b135a39 | ||
|
3baffc9bef | ||
|
d2eef546d5 | ||
|
86afe915f3 | ||
|
1abacacab4 | ||
|
8f28182111 | ||
|
ad2bc1fd89 |
6 changed files with 36 additions and 13 deletions
|
@ -22,7 +22,7 @@ ntfy-alertmanager has support for setting ntfy [priority], [tags], [icon], [acti
|
||||||
(which can be used to create an Alertmanager silence), [email notifications] and [phone calls].
|
(which can be used to create an Alertmanager silence), [email notifications] and [phone calls].
|
||||||
Define a decreasing order of labels in the config file and map those labels to tags, priority, an icon or an email address.
|
Define a decreasing order of labels in the config file and map those labels to tags, priority, an icon or an email address.
|
||||||
|
|
||||||
- For priority and icon the first found value will be chosen. An icon for "resolved" alerts will take precedence.
|
- For priority and icon the first found value will be chosen. Settings for "resolved" alerts will take precedence.
|
||||||
- Tags are added together.
|
- Tags are added together.
|
||||||
|
|
||||||
### Alertmanager config
|
### Alertmanager config
|
||||||
|
|
|
@ -40,6 +40,7 @@ labels {
|
||||||
resolved {
|
resolved {
|
||||||
tags "resolved,partying_face"
|
tags "resolved,partying_face"
|
||||||
icon "https://foo.com/resolved.png"
|
icon "https://foo.com/resolved.png"
|
||||||
|
priority 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ntfy {
|
ntfy {
|
||||||
|
@ -53,7 +54,10 @@ ntfy {
|
||||||
access-token foobar
|
access-token foobar
|
||||||
# When using (self signed) certificates that cannot be verified, you can instead specify
|
# When using (self signed) certificates that cannot be verified, you can instead specify
|
||||||
# the SHA512 fingerprint.
|
# the SHA512 fingerprint.
|
||||||
certificate-fingerprint 136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299
|
# openssl can be used to obtain it:
|
||||||
|
# openssl s_client -connect HOST:PORT | openssl x509 -fingerprint -sha512 -noout
|
||||||
|
# For convenience ntfy-alertmanager will convert the certificate to lower case and remove all colons.
|
||||||
|
certificate-fingerprint 13:6D:2B:88:9C:57:36:D0:81:B4:B2:9C:79:09:27:62:92:CF:B8:6A:6B:D3:AD:46:35:CB:70:17:EB:99:6E:28:08:2A:B8:C6:79:4B:F6:2E:81:79:41:98:1D:53:C8:07:B3:5C:24:5F:B1:8E:B6:FB:66:B5:DD:B4:D0:5C:29:91
|
||||||
# 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.
|
||||||
|
|
|
@ -77,8 +77,9 @@ type alertmanagerConfig struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolvedConfig struct {
|
type resolvedConfig struct {
|
||||||
Tags []string
|
Tags []string
|
||||||
Icon string
|
Icon string
|
||||||
|
Priority string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadConfig reads an scfg formatted file and returns the configuration struct.
|
// ReadConfig reads an scfg formatted file and returns the configuration struct.
|
||||||
|
@ -283,6 +284,9 @@ func ReadConfig(path string) (*Config, error) {
|
||||||
if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil {
|
if err := d.ParseParams(&config.Ntfy.CertFingerprint); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hex.EncodeToString outputs a lower case string
|
||||||
|
config.Ntfy.CertFingerprint = strings.ToLower(strings.ReplaceAll(config.Ntfy.CertFingerprint, ":", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
d = ntfyDir.Children.Get("email-address")
|
d = ntfyDir.Children.Get("email-address")
|
||||||
|
@ -412,6 +416,13 @@ func ReadConfig(path string) (*Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d = resolvedDir.Children.Get("priority")
|
||||||
|
if d != nil {
|
||||||
|
if err := d.ParseParams(&config.Resolved.Priority); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|
|
@ -41,11 +41,12 @@ labels {
|
||||||
resolved {
|
resolved {
|
||||||
tags "resolved,partying_face"
|
tags "resolved,partying_face"
|
||||||
icon "https://foo.com/resolved.png"
|
icon "https://foo.com/resolved.png"
|
||||||
|
priority 1
|
||||||
}
|
}
|
||||||
|
|
||||||
ntfy {
|
ntfy {
|
||||||
topic https://ntfy.sh/alertmanager-alerts
|
topic https://ntfy.sh/alertmanager-alerts
|
||||||
certificate-fingerprint 136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299
|
certificate-fingerprint 13:6D:2B:88:9C:57:36:D0:81:B4:B2:9C:79:09:27:62:92:CF:B8:6A:6B:D3:AD:46:35:CB:70:17:EB:99:6E:28:08:2A:B8:C6:79:4B:F6:2E:81:79:41:98:1D:53:C8:07:B3:5C:24:5F:B1:8E:B6:FB:66:B5:DD:B4:D0:5C:29:91
|
||||||
user user
|
user user
|
||||||
password pass
|
password pass
|
||||||
}
|
}
|
||||||
|
@ -76,7 +77,7 @@ cache {
|
||||||
Topic: "https://ntfy.sh/alertmanager-alerts",
|
Topic: "https://ntfy.sh/alertmanager-alerts",
|
||||||
User: "user",
|
User: "user",
|
||||||
Password: "pass",
|
Password: "pass",
|
||||||
CertFingerprint: "136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c299",
|
CertFingerprint: "136d2b889c5736d081b4b29c7909276292cfb86a6bd3ad4635cb7017eb996e28082ab8c6794bf62e817941981d53c807b35c245fb18eb6fb66b5ddb4d05c2991",
|
||||||
},
|
},
|
||||||
Labels: labels{Order: []string{"severity", "instance"},
|
Labels: labels{Order: []string{"severity", "instance"},
|
||||||
Label: map[string]labelConfig{
|
Label: map[string]labelConfig{
|
||||||
|
@ -104,8 +105,9 @@ cache {
|
||||||
URL: "https://alertmanager.xenrox.net",
|
URL: "https://alertmanager.xenrox.net",
|
||||||
},
|
},
|
||||||
Resolved: resolvedConfig{
|
Resolved: resolvedConfig{
|
||||||
Tags: []string{"resolved", "partying_face"},
|
Tags: []string{"resolved", "partying_face"},
|
||||||
Icon: "https://foo.com/resolved.png",
|
Icon: "https://foo.com/resolved.png",
|
||||||
|
Priority: "1",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
main.go
14
main.go
|
@ -117,6 +117,7 @@ func (br *bridge) singleAlertNotifications(p *payload) []*notification {
|
||||||
if alert.Status == "resolved" {
|
if alert.Status == "resolved" {
|
||||||
tags = append(tags, br.cfg.Resolved.Tags...)
|
tags = append(tags, br.cfg.Resolved.Tags...)
|
||||||
n.icon = br.cfg.Resolved.Icon
|
n.icon = br.cfg.Resolved.Icon
|
||||||
|
n.priority = br.cfg.Resolved.Priority
|
||||||
}
|
}
|
||||||
|
|
||||||
n.emailAddress = br.cfg.Ntfy.EmailAddress
|
n.emailAddress = br.cfg.Ntfy.EmailAddress
|
||||||
|
@ -226,6 +227,7 @@ func (br *bridge) multiAlertNotification(p *payload) *notification {
|
||||||
if p.Status == "resolved" {
|
if p.Status == "resolved" {
|
||||||
tags = append(tags, br.cfg.Resolved.Tags...)
|
tags = append(tags, br.cfg.Resolved.Tags...)
|
||||||
n.icon = br.cfg.Resolved.Icon
|
n.icon = br.cfg.Resolved.Icon
|
||||||
|
n.priority = br.cfg.Resolved.Priority
|
||||||
}
|
}
|
||||||
|
|
||||||
n.emailAddress = br.cfg.Ntfy.EmailAddress
|
n.emailAddress = br.cfg.Ntfy.EmailAddress
|
||||||
|
@ -349,7 +351,14 @@ func (br *bridge) publish(n *notification) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha512.Sum512(rawCerts[0])
|
hash := sha512.Sum512(rawCerts[0])
|
||||||
return fmt.Errorf("ntfy certificate fingerprint does not match: expected %q, got %q", hex.EncodeToString(hash[:]), configFingerprint)
|
var expectedFingerprint string
|
||||||
|
for i, b := range hash {
|
||||||
|
if i != 0 {
|
||||||
|
expectedFingerprint += ":"
|
||||||
|
}
|
||||||
|
expectedFingerprint += fmt.Sprintf("%02X", b)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("the ntfy certificate fingerprint (%s) is not set in the config", expectedFingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsCfg.InsecureSkipVerify = true
|
tlsCfg.InsecureSkipVerify = true
|
||||||
|
@ -377,8 +386,6 @@ func (br *bridge) publish(n *notification) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *bridge) handleWebhooks(w http.ResponseWriter, r *http.Request) {
|
func (br *bridge) handleWebhooks(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
logger := br.logger.With(slog.String("handler", "/"))
|
logger := br.logger.With(slog.String("handler", "/"))
|
||||||
|
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
@ -396,6 +403,7 @@ func (br *bridge) handleWebhooks(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
var event payload
|
var event payload
|
||||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||||
|
http.Error(w, "Failed to parse payload", http.StatusInternalServerError)
|
||||||
logger.Debug("Failed to decode payload",
|
logger.Debug("Failed to decode payload",
|
||||||
slog.String("error", err.Error()))
|
slog.String("error", err.Error()))
|
||||||
return
|
return
|
||||||
|
|
|
@ -38,8 +38,6 @@ type silenceResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
|
func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) {
|
||||||
defer r.Body.Close()
|
|
||||||
|
|
||||||
logger := br.logger.With(slog.String("handler", "/silences"))
|
logger := br.logger.With(slog.String("handler", "/silences"))
|
||||||
|
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
|
|
Loading…
Reference in a new issue