diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index c00324cdf38..0a3ddb25f87 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -168,6 +168,11 @@ func (srv AlertmanagerSrv) RouteGetSilences(c *models.ReqContext) response.Respo } func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response { + err := body.EncryptSecureSettings() + if err != nil { + return response.Error(http.StatusInternalServerError, "failed to encrypt receiver secrets", err) + } + if err := srv.am.SaveAndApplyConfig(&body); err != nil { return response.Error(http.StatusInternalServerError, "failed to save and apply Alertmanager configuration", err) } diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index af371e642d2..fdc9792900d 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -1,11 +1,14 @@ package definitions import ( + "encoding/base64" "encoding/json" "fmt" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" "github.com/prometheus/alertmanager/config" "gopkg.in/yaml.v3" @@ -242,6 +245,26 @@ func (c *PostableUserConfig) validate() error { return nil } +func (c *PostableUserConfig) EncryptSecureSettings() error { + // encrypt secure settings for storing them in DB + for _, r := range c.AlertmanagerConfig.Receivers { + switch r.Type() { + case GrafanaReceiverType: + for _, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { + for k, v := range gr.SecureSettings { + encryptedData, err := util.Encrypt([]byte(v), setting.SecretKey) + if err != nil { + return fmt.Errorf("failed to encrypt secure settings: %w", err) + } + gr.SecureSettings[k] = base64.StdEncoding.EncodeToString(encryptedData) + } + } + default: + } + } + return nil +} + // MarshalYAML implements yaml.Marshaller. func (c *PostableUserConfig) MarshalYAML() (interface{}, error) { yml, err := yaml.Marshal(c.amSimple) diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index dd25bee70d5..85d5bb22c90 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -3,6 +3,7 @@ package notifier import ( "context" "crypto/md5" + "encoding/base64" "encoding/json" "errors" "fmt" @@ -223,7 +224,7 @@ func (am *Alertmanager) SaveAndApplyConfig(cfg *apimodels.PostableUserConfig) er if err := am.Store.SaveAlertmanagerConfiguration(cmd); err != nil { return fmt.Errorf("failed to save Alertmanager configuration: %w", err) } - if err := am.applyConfig(cfg); err != nil { + if err := am.applyConfig(cfg, rawConfig); err != nil { return fmt.Errorf("unable to reload configuration: %w", err) } @@ -252,32 +253,27 @@ func (am *Alertmanager) SyncAndApplyConfigFromDatabase() error { return err } - if err := am.applyConfig(cfg); err != nil { + if err := am.applyConfig(cfg, nil); err != nil { return fmt.Errorf("unable to reload configuration: %w", err) } return nil } -// ApplyConfig applies a new configuration by re-initializing all components using the configuration provided. -func (am *Alertmanager) ApplyConfig(cfg *apimodels.PostableUserConfig) error { - am.reloadConfigMtx.Lock() - defer am.reloadConfigMtx.Unlock() - - return am.applyConfig(cfg) -} - const defaultTemplate = "templates/default.tmpl" // applyConfig applies a new configuration by re-initializing all components using the configuration provided. // It is not safe to call concurrently. -func (am *Alertmanager) applyConfig(cfg *apimodels.PostableUserConfig) error { +func (am *Alertmanager) applyConfig(cfg *apimodels.PostableUserConfig, rawConfig []byte) error { // First, let's make sure this config is not already loaded var configChanged bool - rawConfig, err := json.Marshal(cfg.AlertmanagerConfig) - if err != nil { - // In theory, this should never happen. - return err + if rawConfig == nil { + enc, err := json.Marshal(cfg.AlertmanagerConfig) + if err != nil { + // In theory, this should never happen. + return err + } + rawConfig = enc } if md5.Sum(am.config) != md5.Sum(rawConfig) { @@ -380,6 +376,16 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp var integrations []notify.Integration for i, r := range receiver.GrafanaManagedReceivers { + // secure settings are already encrypted at this point + secureSettings := securejsondata.SecureJsonData(make(map[string][]byte, len(r.SecureSettings))) + + for k, v := range r.SecureSettings { + d, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return nil, fmt.Errorf("failed to decode secure setting") + } + secureSettings[k] = d + } var ( cfg = &models.AlertNotification{ Uid: r.Uid, @@ -389,7 +395,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp SendReminder: r.SendReminder, DisableResolveMessage: r.DisableResolveMessage, Settings: r.Settings, - SecureSettings: securejsondata.GetEncryptedJsonData(r.SecureSettings), + SecureSettings: secureSettings, } n NotificationChannel err error @@ -409,6 +415,8 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp n, err = channels.NewDingDingNotifier(cfg, tmpl) case "webhook": n, err = channels.NewWebHookNotifier(cfg, tmpl) + default: + return nil, fmt.Errorf("notifier %s is not supported", r.Type) } if err != nil { return nil, err