Add support for reading PagerDuty secrets from files (#3107)
* Add support for reading PagerDuty secrets from files * Update documentation Signed-off-by: Oktarian Tilney-Bassett <oktariantilneybassett@improbable.io>
This commit is contained in:
parent
d034f116d5
commit
1045dc0f21
|
@ -208,20 +208,22 @@ type PagerdutyConfig struct {
|
|||
|
||||
HTTPConfig *commoncfg.HTTPClientConfig `yaml:"http_config,omitempty" json:"http_config,omitempty"`
|
||||
|
||||
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
|
||||
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
|
||||
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
|
||||
Client string `yaml:"client,omitempty" json:"client,omitempty"`
|
||||
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
|
||||
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
||||
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
|
||||
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
|
||||
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
|
||||
Class string `yaml:"class,omitempty" json:"class,omitempty"`
|
||||
Component string `yaml:"component,omitempty" json:"component,omitempty"`
|
||||
Group string `yaml:"group,omitempty" json:"group,omitempty"`
|
||||
ServiceKey Secret `yaml:"service_key,omitempty" json:"service_key,omitempty"`
|
||||
ServiceKeyFile string `yaml:"service_key_file,omitempty" json:"service_key_file,omitempty"`
|
||||
RoutingKey Secret `yaml:"routing_key,omitempty" json:"routing_key,omitempty"`
|
||||
RoutingKeyFile string `yaml:"routing_key_file,omitempty" json:"routing_key_file,omitempty"`
|
||||
URL *URL `yaml:"url,omitempty" json:"url,omitempty"`
|
||||
Client string `yaml:"client,omitempty" json:"client,omitempty"`
|
||||
ClientURL string `yaml:"client_url,omitempty" json:"client_url,omitempty"`
|
||||
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
||||
Details map[string]string `yaml:"details,omitempty" json:"details,omitempty"`
|
||||
Images []PagerdutyImage `yaml:"images,omitempty" json:"images,omitempty"`
|
||||
Links []PagerdutyLink `yaml:"links,omitempty" json:"links,omitempty"`
|
||||
Source string `yaml:"source,omitempty" json:"source,omitempty"`
|
||||
Severity string `yaml:"severity,omitempty" json:"severity,omitempty"`
|
||||
Class string `yaml:"class,omitempty" json:"class,omitempty"`
|
||||
Component string `yaml:"component,omitempty" json:"component,omitempty"`
|
||||
Group string `yaml:"group,omitempty" json:"group,omitempty"`
|
||||
}
|
||||
|
||||
// PagerdutyLink is a link
|
||||
|
@ -244,9 +246,15 @@ func (c *PagerdutyConfig) UnmarshalYAML(unmarshal func(interface{}) error) error
|
|||
if err := unmarshal((*plain)(c)); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.RoutingKey == "" && c.ServiceKey == "" {
|
||||
if c.RoutingKey == "" && c.ServiceKey == "" && c.RoutingKeyFile == "" && c.ServiceKeyFile == "" {
|
||||
return fmt.Errorf("missing service or routing key in PagerDuty config")
|
||||
}
|
||||
if len(c.RoutingKey) > 0 && len(c.RoutingKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of routing_key & routing_key_file must be configured")
|
||||
}
|
||||
if len(c.ServiceKey) > 0 && len(c.ServiceKeyFile) > 0 {
|
||||
return fmt.Errorf("at most one of service_key & service_key_file must be configured")
|
||||
}
|
||||
if c.Details == nil {
|
||||
c.Details = make(map[string]string)
|
||||
}
|
||||
|
|
|
@ -59,38 +59,78 @@ headers:
|
|||
}
|
||||
}
|
||||
|
||||
func TestPagerdutyRoutingKeyIsPresent(t *testing.T) {
|
||||
in := `
|
||||
func TestPagerdutyTestRoutingKey(t *testing.T) {
|
||||
t.Run("error if no routing key or key file", func(t *testing.T) {
|
||||
in := `
|
||||
routing_key: ''
|
||||
`
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
|
||||
expected := "missing service or routing key in PagerDuty config"
|
||||
expected := "missing service or routing key in PagerDuty config"
|
||||
|
||||
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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error if both routing key and key file", func(t *testing.T) {
|
||||
in := `
|
||||
routing_key: 'xyz'
|
||||
routing_key_file: 'xyz'
|
||||
`
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
|
||||
expected := "at most one of routing_key & routing_key_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 TestPagerdutyServiceKeyIsPresent(t *testing.T) {
|
||||
in := `
|
||||
func TestPagerdutyServiceKey(t *testing.T) {
|
||||
t.Run("error if no service key or key file", func(t *testing.T) {
|
||||
in := `
|
||||
service_key: ''
|
||||
`
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
|
||||
expected := "missing service or routing key in PagerDuty config"
|
||||
expected := "missing service or routing key in PagerDuty config"
|
||||
|
||||
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())
|
||||
}
|
||||
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())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("error if both service key and key file", func(t *testing.T) {
|
||||
in := `
|
||||
service_key: 'xyz'
|
||||
service_key_file: 'xyz'
|
||||
`
|
||||
var cfg PagerdutyConfig
|
||||
err := yaml.UnmarshalStrict([]byte(in), &cfg)
|
||||
|
||||
expected := "at most one of service_key & service_key_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 TestPagerdutyDetails(t *testing.T) {
|
||||
|
|
|
@ -640,11 +640,19 @@ PagerDuty provides [documentation](https://www.pagerduty.com/docs/guides/prometh
|
|||
# Whether to notify about resolved alerts.
|
||||
[ send_resolved: <boolean> | default = true ]
|
||||
|
||||
# The following two options are mutually exclusive.
|
||||
# The routing and service keys are mutually exclusive.
|
||||
# The PagerDuty integration key (when using PagerDuty integration type `Events API v2`).
|
||||
# It is mutually exclusive with `routing_key_file`.
|
||||
routing_key: <tmpl_secret>
|
||||
# Read the Pager Duty routing key from a file.
|
||||
# It is mutually exclusive with `routing_key`.
|
||||
routing_key_file: <filepath>
|
||||
# The PagerDuty integration key (when using PagerDuty integration type `Prometheus`).
|
||||
# It is mutually exclusive with `service_key_file`.
|
||||
service_key: <tmpl_secret>
|
||||
# Read the Pager Duty service key from a file.
|
||||
# It is mutually exclusive with `service_key`.
|
||||
service_key_file: <filepath>
|
||||
|
||||
# The URL to send API requests to
|
||||
[ url: <string> | default = global.pagerduty_url ]
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
|
@ -54,7 +55,7 @@ func New(c *config.PagerdutyConfig, t *template.Template, l log.Logger, httpOpts
|
|||
return nil, err
|
||||
}
|
||||
n := &Notifier{conf: c, tmpl: t, logger: l, client: client}
|
||||
if c.ServiceKey != "" {
|
||||
if c.ServiceKey != "" || c.ServiceKeyFile != "" {
|
||||
n.apiV1 = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
|
||||
// Retrying can solve the issue on 403 (rate limiting) and 5xx response codes.
|
||||
// https://v2.developer.pagerduty.com/docs/trigger-events
|
||||
|
@ -153,8 +154,17 @@ func (n *Notifier) notifyV1(
|
|||
level.Debug(n.logger).Log("msg", "Truncated description", "description", description, "key", key)
|
||||
}
|
||||
|
||||
serviceKey := string(n.conf.ServiceKey)
|
||||
if serviceKey == "" {
|
||||
content, fileErr := os.ReadFile(n.conf.ServiceKeyFile)
|
||||
if fileErr != nil {
|
||||
return false, errors.Wrap(fileErr, "failed to read service key from file")
|
||||
}
|
||||
serviceKey = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
msg := &pagerDutyMessage{
|
||||
ServiceKey: tmpl(string(n.conf.ServiceKey)),
|
||||
ServiceKey: tmpl(serviceKey),
|
||||
EventType: eventType,
|
||||
IncidentKey: key.Hash(),
|
||||
Description: description,
|
||||
|
@ -209,10 +219,19 @@ func (n *Notifier) notifyV2(
|
|||
level.Debug(n.logger).Log("msg", "Truncated summary", "summary", summary, "key", key)
|
||||
}
|
||||
|
||||
routingKey := string(n.conf.RoutingKey)
|
||||
if routingKey == "" {
|
||||
content, fileErr := os.ReadFile(n.conf.RoutingKeyFile)
|
||||
if fileErr != nil {
|
||||
return false, errors.Wrap(fileErr, "failed to read routing key from file")
|
||||
}
|
||||
routingKey = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
msg := &pagerDutyMessage{
|
||||
Client: tmpl(n.conf.Client),
|
||||
ClientURL: tmpl(n.conf.ClientURL),
|
||||
RoutingKey: tmpl(string(n.conf.RoutingKey)),
|
||||
RoutingKey: tmpl(routingKey),
|
||||
EventAction: eventType,
|
||||
DedupKey: key.Hash(),
|
||||
Images: make([]pagerDutyImage, 0, len(n.conf.Images)),
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -111,6 +112,54 @@ func TestPagerDutyRedactedURLV2(t *testing.T) {
|
|||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyV1ServiceKeyFromFile(t *testing.T) {
|
||||
key := "01234567890123456789012345678901"
|
||||
f, err := os.CreateTemp("", "pagerduty_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(key)
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
ServiceKeyFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
notifier.apiV1 = u.String()
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyV2RoutingKeyFromFile(t *testing.T) {
|
||||
key := "01234567890123456789012345678901"
|
||||
f, err := os.CreateTemp("", "pagerduty_test")
|
||||
require.NoError(t, err, "creating temp file failed")
|
||||
_, err = f.WriteString(key)
|
||||
require.NoError(t, err, "writing to temp file failed")
|
||||
|
||||
ctx, u, fn := test.GetContextWithCancelingURL()
|
||||
defer fn()
|
||||
|
||||
notifier, err := New(
|
||||
&config.PagerdutyConfig{
|
||||
URL: &config.URL{URL: u},
|
||||
RoutingKeyFile: f.Name(),
|
||||
HTTPConfig: &commoncfg.HTTPClientConfig{},
|
||||
},
|
||||
test.CreateTmpl(t),
|
||||
log.NewNopLogger(),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
test.AssertNotifyLeaksNoSecret(ctx, t, notifier, key)
|
||||
}
|
||||
|
||||
func TestPagerDutyTemplating(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
|
|
Loading…
Reference in New Issue