Alerting: Copy alertmanager configuration before decrypting (#105271)
This commit is contained in:
@@ -285,7 +285,7 @@ func (am *Alertmanager) isDefaultConfiguration(configHash [16]byte) bool {
|
||||
return fmt.Sprintf("%x", configHash) == am.defaultConfigHash
|
||||
}
|
||||
|
||||
// decryptConfiguration decrypts the configuration in-place and returns the decrypted configuration alongside its hash.
|
||||
// decryptConfiguration creates a copy of the configuration, decrypts it, and returns the decrypted configuration alongside its hash.
|
||||
// Should not be used outside of this package and the specific use case of decrypting the configuration before sending
|
||||
// it to the remote Alertmanager.
|
||||
func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels.PostableUserConfig) ([]byte, [16]byte, error) {
|
||||
@@ -293,10 +293,18 @@ func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels
|
||||
return am.decrypt(ctx, payload)
|
||||
}
|
||||
|
||||
// Iterate through receivers and decrypt secure settings.
|
||||
// It's not necessary to be careful about not modifying the original, as it's used only in a specific context where
|
||||
// the config is read from json and then immediately sent to the remote Alertmanager.
|
||||
for _, rcv := range cfg.AlertmanagerConfig.Receivers {
|
||||
// Create a copy of the configuration to avoid modifying the original
|
||||
cfgCopy := &apimodels.PostableUserConfig{}
|
||||
rawCfg, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
return nil, [16]byte{}, fmt.Errorf("unable to marshal original configuration: %w", err)
|
||||
}
|
||||
if err := json.Unmarshal(rawCfg, cfgCopy); err != nil {
|
||||
return nil, [16]byte{}, fmt.Errorf("unable to unmarshal original configuration: %w", err)
|
||||
}
|
||||
|
||||
// Iterate through receivers and decrypt secure settings on the copy
|
||||
for _, rcv := range cfgCopy.AlertmanagerConfig.Receivers {
|
||||
for _, gmr := range rcv.GrafanaManagedReceivers {
|
||||
decrypted, err := gmr.DecryptSecureSettings(fn)
|
||||
if err != nil {
|
||||
@@ -306,7 +314,7 @@ func (am *Alertmanager) decryptConfiguration(ctx context.Context, cfg *apimodels
|
||||
}
|
||||
}
|
||||
|
||||
rawDecrypted, err := json.Marshal(cfg)
|
||||
rawDecrypted, err := json.Marshal(cfgCopy)
|
||||
if err != nil {
|
||||
return nil, [16]byte{}, fmt.Errorf("unable to marshal decrypted configuration: %w", err)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,18 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
testPasswordBase64 = base64.StdEncoding.EncodeToString([]byte(testPassword))
|
||||
defaultGrafanaConfig = setting.GetAlertmanagerDefaultConfiguration()
|
||||
errTest = errors.New("test")
|
||||
|
||||
// Valid Grafana Alertmanager configuration with secret in base64.
|
||||
testGrafanaConfigWithEncryptedSecret = fmt.Sprintf(`{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"%s"}}]}]}}`, testPasswordBase64)
|
||||
)
|
||||
|
||||
const (
|
||||
testPassword = "test"
|
||||
|
||||
// Valid Grafana Alertmanager configurations.
|
||||
testGrafanaConfig = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"","name":"some other name","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"}}]}]}}`
|
||||
testGrafanaConfigWithSecret = `{"template_files":{},"alertmanager_config":{"time_intervals":[{"name":"weekends","time_intervals":[{"weekdays":["saturday","sunday"],"location":"Africa/Accra"}]}],"route":{"receiver":"grafana-default-email","group_by":["grafana_folder","alertname"]},"receivers":[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"dde6ntuob69dtf","name":"WH","type":"webhook","disableResolveMessage":false,"settings":{"url":"http://localhost:8080","username":"test"},"secureSettings":{"password":"test"}}]}]}}`
|
||||
@@ -56,11 +67,6 @@ const (
|
||||
testNflog2 = "OgoqCgZncm91cDISEgoJcmVjZWl2ZXIyEgV0ZXN0MyoMCLSDkbAGEMvaofYCEgwIxJ+RsAYQy9qh9gI6CioKBmdyb3VwMRISCglyZWNlaXZlcjESBXRlc3QzKgwItIORsAYQy9qh9gISDAjEn5GwBhDL2qH2Ag=="
|
||||
)
|
||||
|
||||
var (
|
||||
defaultGrafanaConfig = setting.GetAlertmanagerDefaultConfiguration()
|
||||
errTest = errors.New("test")
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
testsuite.Run(m)
|
||||
}
|
||||
@@ -505,6 +511,44 @@ func Test_isDefaultConfiguration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptConfiguration(t *testing.T) {
|
||||
t.Run("should not modify the original config", func(t *testing.T) {
|
||||
var inputCfg apimodels.PostableUserConfig
|
||||
require.NoError(t, json.Unmarshal([]byte(testGrafanaConfigWithEncryptedSecret), &inputCfg))
|
||||
|
||||
decryptFn := func(_ context.Context, payload []byte) ([]byte, error) {
|
||||
if string(payload) == testPassword {
|
||||
return []byte(testPassword), nil
|
||||
}
|
||||
return nil, fmt.Errorf("incorrect payload")
|
||||
}
|
||||
|
||||
am := &Alertmanager{
|
||||
decrypt: decryptFn,
|
||||
}
|
||||
|
||||
rawDecrypted, _, err := am.decryptConfiguration(context.Background(), &inputCfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
currentJSON, err := json.Marshal(inputCfg)
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, testGrafanaConfigWithEncryptedSecret, string(currentJSON), "Original configuration should not be modified")
|
||||
|
||||
var decryptedCfg apimodels.PostableUserConfig
|
||||
require.NoError(t, json.Unmarshal(rawDecrypted, &decryptedCfg))
|
||||
|
||||
found := false
|
||||
for _, rcv := range decryptedCfg.AlertmanagerConfig.Receivers {
|
||||
for _, gmr := range rcv.GrafanaManagedReceivers {
|
||||
if gmr.Type == "webhook" && gmr.SecureSettings["password"] == testPassword {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
}
|
||||
require.True(t, found, "Decrypted configuration should contain decrypted password")
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationRemoteAlertmanagerConfiguration(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
|
||||
Reference in New Issue
Block a user