[release-11.5.3] Alerting: Re-encrypt existing contact points before get and patch in legacy config API (#101819)

Alerting: Re-encrypt existing contact points before get and patch in legacy config API (#101263)

* Test covering Get+Save interaction for newly secret fields

* Alerting: Re-encrypt existing contact points before get and patch

(cherry picked from commit b73c59547c)

Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
This commit is contained in:
grafana-delivery-bot[bot]
2025-03-07 17:23:45 -05:00
committed by GitHub
co-authored by Matthew Jacobson
parent ff47af1fba
commit 7abc79cb2e
4 changed files with 218 additions and 16 deletions
@@ -3,6 +3,7 @@ package api
import (
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
"net/http"
"testing"
@@ -12,6 +13,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
@@ -259,6 +261,118 @@ func TestAlertmanagerConfig(t *testing.T) {
})
}
func TestGetAlertmanagerConfiguration_NewSecretField(t *testing.T) {
// This test has the following goals:
// Given:
// - A saved notifier config with an existing secret field stored unencrypted in Settings.
// Ensure:
// - The secret field is not returned in plaintext.
// - The secret field is returned as a bool in SecureFields.
// - The secret field is correctly saved and encrypted in SecureSettings when saving the notifier config without changes.
// - The secret field is removed from Settings when saving the notifier config.
sut := createSut(t)
orgId := int64(1)
// This config has the secret field "integrationKey" stored incorrectly and unencrypted in Settings.
configs := map[int64]*ngmodels.AlertConfiguration{
1: {
OrgID: orgId,
AlertmanagerConfiguration: `{
"alertmanager_config": {
"route": {
"receiver": "configWithNewlySecretSetting"
},
"receivers": [{
"name": "configWithNewlySecretSetting",
"grafana_managed_receiver_configs": [{
"uid": "configWithNewlySecretSetting-uid",
"name": "configWithNewlySecretSetting",
"type": "pagerduty",
"settings": {"integrationKey": "unencrypted secure secret"},
"secureSettings": {}
}]
}]
}
}
`,
CreatedAt: time.Now().Unix(),
Default: false,
},
}
// Store the config as-is in the database. Bypasses normal save route so it doesn't get pre-emptively fixed.
mam := createMultiOrgAlertmanager(t, configs)
sut.mam = mam
rc := createRequestCtxInOrg(orgId)
res := sut.RouteGetAlertingConfig(rc)
gettable := asGettableUserConfig(t, res)
integration := gettable.GetGrafanaReceiverMap()["configWithNewlySecretSetting-uid"]
require.NotNil(t, integration)
var settings map[string]string
err := json.Unmarshal(integration.Settings, &settings)
require.NoError(t, err)
// The secret field "integrationKey" should not be returned in plaintext.
assert.NotEqual(t, "unencrypted secure secret", settings["integrationKey"])
// Just in case let's look for the unencrypted value anywhere in the settings.
assert.NotContains(t, string(integration.Settings), "unencrypted")
// The secret fields should be returned as a bool in SecureFields.
assert.True(t, integration.SecureFields["integrationKey"])
// Now we save the config without changes. This should encrypt the field "integrationKey" into SecureSettings and
// remove it from Settings.
// Simulates FE-API interaction, "integrationKey" is not sent in Settings as the caller.
// Instead, it leaves it out of "SecureSettings" to indicate the API should keep the existing value.
var postWithoutChanges = `{
"alertmanager_config": {
"route": {
"receiver": "configWithNewlySecretSetting"
},
"receivers": [{
"name": "configWithNewlySecretSetting",
"grafana_managed_receiver_configs": [{
"uid": "configWithNewlySecretSetting-uid",
"name": "configWithNewlySecretSetting",
"type": "pagerduty",
"settings": {},
"secureSettings": {}
}]
}]
}
}
`
postable := createAmConfigRequest(t, postWithoutChanges)
res = sut.RoutePostAlertingConfig(rc, postable)
require.Equal(t, 202, res.Status())
// Check that the secret field "integrationKey" is now encrypted in SecureSettings.
savedConfig := &apimodels.PostableUserConfig{}
err = json.Unmarshal([]byte(configs[orgId].AlertmanagerConfiguration), savedConfig)
require.NoError(t, err)
savedIntegration := savedConfig.GetGrafanaReceiverMap()["configWithNewlySecretSetting-uid"]
require.NotNil(t, savedIntegration)
// No longer in Settings.
assert.Equal(t, "{}", string(savedIntegration.Settings))
// Encrypted in SecureSettings.
secureSecret := savedIntegration.SecureSettings["integrationKey"]
assert.NotEmpty(t, secureSecret)
encryptedSecret, err := base64.StdEncoding.DecodeString(secureSecret)
require.NoError(t, err)
// No access to .Decrypt, but we can check that it's not the same as the unencrypted value.
assert.NotEqual(t, "unencrypted secure secret", string(encryptedSecret))
}
func TestAlertmanagerAutogenConfig(t *testing.T) {
createSutForAutogen := func(t *testing.T) (AlertmanagerSrv, map[int64]*ngmodels.AlertConfiguration) {
sut := createSut(t)