Alerting: Fix contact point testing with secure settings (#72235)

* Alerting: Fix contact point testing with secure settings

Fixes double encryption of secure settings during contact point testing and removes code duplication
that helped cause the drift between alertmanager and test endpoint. Also adds integration tests to cover
the regression.

Note: provisioningStore is created to remove cycle and the unnecessary dependency.
This commit is contained in:
Matthew Jacobson
2023-07-25 10:04:27 -04:00
committed by GitHub
parent fe77d039e6
commit d31d175109
8 changed files with 277 additions and 99 deletions
+1 -8
View File
@@ -19,7 +19,6 @@ import (
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/util"
)
@@ -302,17 +301,11 @@ func (srv AlertmanagerSrv) RouteGetReceivers(c *contextmodel.ReqContext) respons
}
func (srv AlertmanagerSrv) RoutePostTestReceivers(c *contextmodel.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response {
if err := srv.crypto.LoadSecureSettings(c.Req.Context(), c.OrgID, body.Receivers); err != nil {
if err := srv.crypto.ProcessSecureSettings(c.Req.Context(), c.OrgID, body.Receivers); err != nil {
var unknownReceiverError UnknownReceiverError
if errors.As(err, &unknownReceiverError) {
return ErrResp(http.StatusBadRequest, err, "")
}
return ErrResp(http.StatusInternalServerError, err, "")
}
if err := body.ProcessConfig(func(ctx context.Context, payload []byte) ([]byte, error) {
return srv.crypto.Encrypt(ctx, payload, secrets.WithoutScope())
}); err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration")
}
@@ -1170,7 +1170,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
// Encrypt secure settings.
c, err := notifier.Load([]byte(testConfig))
require.NoError(t, err)
err = c.EncryptConfig(func(ctx context.Context, payload []byte) ([]byte, error) {
err = notifier.EncryptReceiverConfigs(c.AlertmanagerConfig.Receivers, func(ctx context.Context, payload []byte) ([]byte, error) {
return secretsService.Encrypt(ctx, payload, secrets.WithoutScope())
})
require.NoError(t, err)
@@ -2,7 +2,6 @@ package definitions
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
@@ -16,8 +15,6 @@ import (
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/common/model"
"gopkg.in/yaml.v3"
"github.com/grafana/grafana/pkg/util"
)
// swagger:route POST /api/alertmanager/grafana/config/api/v1/alerts alertmanager RoutePostGrafanaAlertingConfig
@@ -276,14 +273,6 @@ type TestReceiversConfigBodyParams struct {
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
func (c *TestReceiversConfigBodyParams) ProcessConfig(encrypt EncryptFn) error {
err := encryptReceiverConfigs(c.Receivers, encrypt)
if err != nil {
return err
}
return assignReceiverConfigsUIDs(c.Receivers)
}
type TestReceiversConfigAlertParams struct {
Annotations model.LabelSet `yaml:"annotations,omitempty" json:"annotations,omitempty"`
Labels model.LabelSet `yaml:"labels,omitempty" json:"labels,omitempty"`
@@ -619,16 +608,6 @@ func (c *PostableUserConfig) GetGrafanaReceiverMap() map[string]*PostableGrafana
return UIDs
}
// EncryptConfig parses grafana receivers and encrypts secrets.
func (c *PostableUserConfig) EncryptConfig(encrypt EncryptFn) error {
return encryptReceiverConfigs(c.AlertmanagerConfig.Receivers, encrypt)
}
// AssignMissingConfigUIDs assigns missing UUIDs to receiver configs.
func (c *PostableUserConfig) AssignMissingConfigUIDs() error {
return assignReceiverConfigsUIDs(c.AlertmanagerConfig.Receivers)
}
// MarshalYAML implements yaml.Marshaller.
func (c *PostableUserConfig) MarshalYAML() (interface{}, error) {
yml, err := yaml.Marshal(c.amSimple)
@@ -1294,55 +1273,6 @@ type PostableGrafanaReceivers struct {
type EncryptFn func(ctx context.Context, payload []byte) ([]byte, error)
func encryptReceiverConfigs(c []*PostableApiReceiver, encrypt EncryptFn) error {
// encrypt secure settings for storing them in DB
for _, r := range c {
switch r.Type() {
case GrafanaReceiverType:
for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers {
for k, v := range gr.SecureSettings {
encryptedData, err := encrypt(context.Background(), []byte(v))
if err != nil {
return fmt.Errorf("failed to encrypt secure settings: %w", err)
}
gr.SecureSettings[k] = base64.StdEncoding.EncodeToString(encryptedData)
}
}
default:
}
}
return nil
}
func assignReceiverConfigsUIDs(c []*PostableApiReceiver) error {
seenUIDs := make(map[string]struct{})
// encrypt secure settings for storing them in DB
for _, r := range c {
switch r.Type() {
case GrafanaReceiverType:
for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers {
if gr.UID == "" {
retries := 5
for i := 0; i < retries; i++ {
gen := util.GenerateShortUID()
_, ok := seenUIDs[gen]
if !ok {
gr.UID = gen
break
}
}
if gr.UID == "" {
return fmt.Errorf("all %d attempts to generate UID for receiver have failed; please retry", retries)
}
}
seenUIDs[gr.UID] = struct{}{}
}
default:
}
}
return nil
}
// ObjectMatchers is Matchers with a different Unmarshal and Marshal methods that accept matchers as objects
// that have already been parsed.
type ObjectMatchers labels.Matchers