Security: apply patch 428 (#106710)

* declare dingding url as secret

patch raw settings before parsing because DingDing's config parser does not know about secrets

* fix integration test

---------

Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
pull/106402/head^2
Kevin Minehart 1 month ago committed by GitHub
parent 5f3c04f537
commit 910eb1dd9e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 56
      pkg/services/ngalert/notifier/alertmanager.go
  2. 1
      pkg/services/ngalert/notifier/channels_config/available_channels.go
  3. 2
      pkg/services/ngalert/notifier/channels_config/available_channels_test.go
  4. 9
      pkg/services/ngalert/notifier/testreceivers.go
  5. 6
      pkg/tests/api/alerting/api_notification_channel_test.go

@ -53,6 +53,7 @@ type alertmanager struct {
Store AlertingStore Store AlertingStore
stateStore stateStore stateStore stateStore
DefaultConfiguration string DefaultConfiguration string
decryptFn alertingNotify.GetDecryptedValueFn
} }
// maintenanceOptions represent the options for components that need maintenance on a frequency within the Alertmanager. // maintenanceOptions represent the options for components that need maintenance on a frequency within the Alertmanager.
@ -149,6 +150,7 @@ func NewAlertmanager(ctx context.Context, orgID int64, cfg *setting.Cfg, store A
Store: store, Store: store,
stateStore: stateStore, stateStore: stateStore,
logger: l.New("component", "alertmanager", opts.TenantKey, opts.TenantID), // similar to what the base does logger: l.New("component", "alertmanager", opts.TenantKey, opts.TenantID), // similar to what the base does
decryptFn: decryptFn,
} }
return am, nil return am, nil
@ -335,6 +337,14 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable
return false, nil return false, nil
} }
receivers := PostableApiAlertingConfigToApiReceivers(cfg.AlertmanagerConfig)
for _, recv := range receivers {
err = patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn)
if err != nil {
return false, err
}
}
am.logger.Info("Applying new configuration to Alertmanager", "configHash", fmt.Sprintf("%x", configHash)) am.logger.Info("Applying new configuration to Alertmanager", "configHash", fmt.Sprintf("%x", configHash))
err = am.Base.ApplyConfig(alertingNotify.NotificationsConfiguration{ err = am.Base.ApplyConfig(alertingNotify.NotificationsConfiguration{
RoutingTree: cfg.AlertmanagerConfig.Route.AsAMRoute(), RoutingTree: cfg.AlertmanagerConfig.Route.AsAMRoute(),
@ -342,7 +352,7 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable
MuteTimeIntervals: cfg.AlertmanagerConfig.MuteTimeIntervals, MuteTimeIntervals: cfg.AlertmanagerConfig.MuteTimeIntervals,
TimeIntervals: cfg.AlertmanagerConfig.TimeIntervals, TimeIntervals: cfg.AlertmanagerConfig.TimeIntervals,
Templates: ToTemplateDefinitions(cfg), Templates: ToTemplateDefinitions(cfg),
Receivers: PostableApiAlertingConfigToApiReceivers(cfg.AlertmanagerConfig), Receivers: receivers,
DispatcherLimits: &nilLimits{}, DispatcherLimits: &nilLimits{},
Raw: rawConfig, Raw: rawConfig,
Hash: configHash, Hash: configHash,
@ -355,6 +365,50 @@ func (am *alertmanager) applyConfig(ctx context.Context, cfg *apimodels.Postable
return true, nil return true, nil
} }
func patchNewSecureFields(ctx context.Context, api *alertingNotify.APIReceiver, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error {
for _, integration := range api.Integrations {
switch integration.Type {
case "dingding":
err := patchSettingsFromSecureSettings(ctx, integration, "url", decode, decrypt)
if err != nil {
return err
}
}
}
return nil
}
func patchSettingsFromSecureSettings(ctx context.Context, integration *alertingNotify.GrafanaIntegrationConfig, key string, decode alertingNotify.DecodeSecretsFn, decrypt alertingNotify.GetDecryptedValueFn) error {
if _, ok := integration.SecureSettings[key]; !ok {
return nil
}
decoded, err := decode(integration.SecureSettings)
if err != nil {
return err
}
settings := map[string]any{}
err = json.Unmarshal(integration.Settings, &settings)
if err != nil {
return err
}
currentValue, ok := settings[key]
currentString := ""
if ok {
currentString, _ = currentValue.(string)
}
secretValue := decrypt(ctx, decoded, key, currentString)
if secretValue == currentString {
return nil
}
settings[key] = secretValue
data, err := json.Marshal(settings)
if err != nil {
return err
}
integration.Settings = data
return nil
}
// PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not // PutAlerts receives the alerts and then sends them through the corresponding route based on whenever the alert has a receiver embedded or not
func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error { func (am *alertmanager) PutAlerts(_ context.Context, postableAlerts apimodels.PostableAlerts) error {
alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts)) alerts := make(alertingNotify.PostableAlerts, 0, len(postableAlerts.PostableAlerts))

@ -284,6 +284,7 @@ func GetAvailableNotifiers() []*NotifierPlugin {
Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx", Placeholder: "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx",
PropertyName: "url", PropertyName: "url",
Required: true, Required: true,
Secure: true,
}, },
{ {
Label: "Message Type", Label: "Message Type",

@ -11,7 +11,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) {
receiverType string receiverType string
expectedSecretFields []string expectedSecretFields []string
}{ }{
{receiverType: "dingding", expectedSecretFields: []string{}}, {receiverType: "dingding", expectedSecretFields: []string{"url"}},
{receiverType: "kafka", expectedSecretFields: []string{"password"}}, {receiverType: "kafka", expectedSecretFields: []string{"password"}},
{receiverType: "email", expectedSecretFields: []string{}}, {receiverType: "email", expectedSecretFields: []string{}},
{receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}}, {receiverType: "pagerduty", expectedSecretFields: []string{"integrationKey"}},

@ -24,12 +24,17 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei
SecureSettings: gr.SecureSettings, SecureSettings: gr.SecureSettings,
}) })
} }
receivers = append(receivers, &alertingNotify.APIReceiver{ recv := &alertingNotify.APIReceiver{
ConfigReceiver: r.Receiver, ConfigReceiver: r.Receiver,
GrafanaIntegrations: alertingNotify.GrafanaIntegrations{ GrafanaIntegrations: alertingNotify.GrafanaIntegrations{
Integrations: integrations, Integrations: integrations,
}, },
}) }
err := patchNewSecureFields(ctx, recv, alertingNotify.DecodeSecretsFromBase64, am.decryptFn)
if err != nil {
return nil, 0, err
}
receivers = append(receivers, recv)
} }
a := &alertingNotify.PostableAlert{} a := &alertingNotify.PostableAlert{}
if c.Alert != nil { if c.Alert != nil {

@ -2088,10 +2088,8 @@ var expAlertmanagerConfigFromAPI = `
"name": "dingding_test", "name": "dingding_test",
"type": "dingding", "type": "dingding",
"disableResolveMessage": false, "disableResolveMessage": false,
"settings": { "settings": {},
"url": "http://CHANNEL_ADDR/dingding_recv/dingding_test" "secureFields": {"url": true}
},
"secureFields": {}
} }
] ]
}, },

Loading…
Cancel
Save