support loading webhook URL from a file (#3223)

* support loading webhook URL from a file

/cc #2498

Signed-off-by: Simon Rozet <me@simonrozet.com>

* notify/webhook: add test for reading url from file

Signed-off-by: Simon Rozet <me@simonrozet.com>

* notify/pushover: add tests for reading secrets from files

Signed-off-by: Simon Rozet <me@simonrozet.com>

---------

Signed-off-by: Simon Rozet <me@simonrozet.com>
This commit is contained in:
Simon Rozet 2023-03-03 15:31:14 +01:00 committed by GitHub
parent 44bb5c9bd3
commit 41eb1213bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 120 additions and 8 deletions

View File

@ -473,7 +473,8 @@ type WebhookConfig struct {
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
// URL to send POST request to.
URL *SecretURL `yaml:"url" json:"url"`
URL *SecretURL `yaml:"url" json:"url"`
URLFile string `yaml:"url_file" json:"url_file"`
// MaxAlerts is the maximum number of alerts to be sent per webhook message.
// Alerts exceeding this threshold will be truncated. Setting this to 0
@ -488,11 +489,16 @@ func (c *WebhookConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
if err := unmarshal((*plain)(c)); err != nil {
return err
}
if c.URL == nil {
return fmt.Errorf("missing URL in webhook config")
if c.URL == nil && c.URLFile == "" {
return fmt.Errorf("one of url or url_file must be configured")
}
if c.URL.Scheme != "https" && c.URL.Scheme != "http" {
return fmt.Errorf("scheme required for webhook url")
if c.URL != nil && c.URLFile != "" {
return fmt.Errorf("at most one of url & url_file must be configured")
}
if c.URL != nil {
if c.URL.Scheme != "https" && c.URL.Scheme != "http" {
return fmt.Errorf("scheme required for webhook url")
}
}
return nil
}

View File

@ -228,7 +228,25 @@ func TestWebhookURLIsPresent(t *testing.T) {
var cfg WebhookConfig
err := yaml.UnmarshalStrict([]byte(in), &cfg)
expected := "missing URL in webhook config"
expected := "one of url or url_file must be configured"
if err == nil {
t.Fatalf("no error returned, expected:\n%v", expected)
}
if err.Error() != expected {
t.Errorf("\nexpected:\n%v\ngot:\n%v", expected, err.Error())
}
}
func TestWebhookURLOrURLFile(t *testing.T) {
in := `
url: 'http://example.com'
url_file: 'http://example.com'
`
var cfg WebhookConfig
err := yaml.UnmarshalStrict([]byte(in), &cfg)
expected := "at most one of url & url_file must be configured"
if err == nil {
t.Fatalf("no error returned, expected:\n%v", expected)

View File

@ -862,7 +862,7 @@ Pushover notifications are sent via the [Pushover API](https://pushover.net/api)
# Whether to notify about resolved alerts.
[ send_resolved: <boolean> | default = true ]
# The recipient user's key.
# The recipient user's key.
# user_key and user_key_file are mutually exclusive.
user_key: <secret>
user_key_file: <filepath>
@ -1116,7 +1116,9 @@ The webhook receiver allows configuring a generic receiver.
[ send_resolved: <boolean> | default = true ]
# The endpoint to send HTTP POST requests to.
# url and url_file are mutually exclusive.
url: <secret>
url_file: <filepath>
# The HTTP client's configuration.
[ http_config: <http_config> | default = global.http_config ]

View File

@ -15,6 +15,7 @@ package pushover
import (
"fmt"
"os"
"testing"
"github.com/go-kit/log"
@ -59,3 +60,53 @@ func TestPushoverRedactedURL(t *testing.T) {
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key, token)
}
func TestPushoverReadingUserKeyFromFile(t *testing.T) {
ctx, apiURL, fn := test.GetContextWithCancelingURL()
defer fn()
const userKey = "user key"
f, err := os.CreateTemp("", "pushover_user_key")
require.NoError(t, err, "creating temp file failed")
_, err = f.WriteString(userKey)
require.NoError(t, err, "writing to temp file failed")
notifier, err := New(
&config.PushoverConfig{
UserKeyFile: f.Name(),
Token: config.Secret("token"),
HTTPConfig: &commoncfg.HTTPClientConfig{},
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
notifier.apiURL = apiURL.String()
require.NoError(t, err)
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, userKey)
}
func TestPushoverReadingTokenFromFile(t *testing.T) {
ctx, apiURL, fn := test.GetContextWithCancelingURL()
defer fn()
const token = "token"
f, err := os.CreateTemp("", "pushover_token")
require.NoError(t, err, "creating temp file failed")
_, err = f.WriteString(token)
require.NoError(t, err, "writing to temp file failed")
notifier, err := New(
&config.PushoverConfig{
UserKey: config.Secret("user key"),
TokenFile: f.Name(),
HTTPConfig: &commoncfg.HTTPClientConfig{},
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
notifier.apiURL = apiURL.String()
require.NoError(t, err)
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, token)
}

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
@ -101,7 +102,18 @@ func (n *Notifier) Notify(ctx context.Context, alerts ...*types.Alert) (bool, er
return false, err
}
resp, err := notify.PostJSON(ctx, n.client, n.conf.URL.String(), &buf)
var url string
if n.conf.URL != nil {
url = n.conf.URL.String()
} else {
content, err := os.ReadFile(n.conf.URLFile)
if err != nil {
return false, fmt.Errorf("read url_file: %w", err)
}
url = string(content)
}
resp, err := notify.PostJSON(ctx, n.client, url, &buf)
if err != nil {
return true, notify.RedactURL(err)
}

View File

@ -19,6 +19,7 @@ import (
"io"
"net/http"
"net/url"
"os"
"testing"
"github.com/go-kit/log"
@ -116,3 +117,25 @@ func TestWebhookRedactedURL(t *testing.T) {
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, secret)
}
func TestWebhookReadingURLFromFile(t *testing.T) {
ctx, u, fn := test.GetContextWithCancelingURL()
defer fn()
f, err := os.CreateTemp("", "webhook_url")
require.NoError(t, err, "creating temp file failed")
_, err = f.WriteString(u.String())
require.NoError(t, err, "writing to temp file failed")
notifier, err := New(
&config.WebhookConfig{
URLFile: f.Name(),
HTTPConfig: &commoncfg.HTTPClientConfig{},
},
test.CreateTmpl(t),
log.NewNopLogger(),
)
require.NoError(t, err)
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, u.String())
}