Alerting: Parse secret fields case-insensitively when creating or updating a contact point (#90968)

* Alerting: Handle case-insensitive secret fields in contact point settings
This commit is contained in:
Alexander Akhmetov 2024-08-01 19:03:47 +02:00 committed by GitHub
parent e231211234
commit 3952f627eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 80 additions and 2 deletions

View File

@ -12,6 +12,7 @@ import (
"github.com/prometheus/alertmanager/config"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
@ -516,13 +517,39 @@ func RemoveSecretsForContactPoint(e *apimodels.EmbeddedContactPoint) (map[string
return nil, err
}
for _, secretKey := range secretKeys {
secretValue := e.Settings.Get(secretKey).MustString()
e.Settings.Del(secretKey)
foundSecretKey, secretValue, err := getCaseInsensitive(e.Settings, secretKey)
if err != nil {
return nil, err
}
e.Settings.Del(foundSecretKey)
s[secretKey] = secretValue
}
return s, nil
}
// getCaseInsensitive returns the value of the specified key, preferring an exact match but accepting a case-insensitive match.
// If no key matches, the second return value is an empty string.
func getCaseInsensitive(jsonObj *simplejson.Json, key string) (string, string, error) {
// Check for an exact key match first.
if value, ok := jsonObj.CheckGet(key); ok {
return key, value.MustString(), nil
}
// If no exact match is found, look for a case-insensitive match.
settingsMap, err := jsonObj.Map()
if err != nil {
return "", "", err
}
for k, v := range settingsMap {
if strings.EqualFold(k, key) {
return k, v.(string), nil
}
}
return key, "", nil
}
// convertRecSvcErr converts errors from notifier.ReceiverService to errors expected from ContactPointService.
func convertRecSvcErr(err error) error {
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {

View File

@ -40,6 +40,11 @@ func TestContactPointService(t *testing.T) {
accesscontrol.ActionAlertingProvisioningRead: nil,
},
}}
decryptedUser := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{
1: {
accesscontrol.ActionAlertingProvisioningReadSecrets: nil,
},
}}
t.Run("service gets contact points from AM config", func(t *testing.T) {
sut := createContactPointServiceSut(t, secretsService)
@ -265,6 +270,52 @@ func TestContactPointService(t *testing.T) {
intercepted := fakeConfigStore.LastSaveCommand
require.Equal(t, expectedConcurrencyToken, intercepted.FetchedConfigurationHash)
})
t.Run("secrets are parsed in a case-insensitive way", func(t *testing.T) {
// JSON unmarshalling is case-insensitive. This means we can have
// a setting named "TOKEN" instead of "token". This test ensures that
// we handle such cases correctly and the token value is properly parsed,
// even if the setting key does not match the JSON key exactly.
tests := []struct {
settingsJSON string
expectedValue string
name string
}{
{
settingsJSON: `{"recipient":"value_recipient","TOKEN":"some-other-token"}`,
expectedValue: "some-other-token",
name: "token key is uppercased",
},
// This test checks that if multiple token keys are present in the settings,
// the key with the exact matching name is used.
{
settingsJSON: `{"recipient":"value_recipient","TOKEN":"some-other-token", "token": "second-token"}`,
expectedValue: "second-token",
name: "multiple token keys",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
sut := createContactPointServiceSut(t, secretsService)
newCp := createTestContactPoint()
settings, _ := simplejson.NewJson([]byte(tc.settingsJSON))
newCp.Settings = settings
_, err := sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.NoError(t, err)
q := cpsQueryWithName(1, newCp.Name)
q.Decrypt = true
cps, err := sut.GetContactPoints(context.Background(), q, decryptedUser)
require.NoError(t, err)
require.Len(t, cps, 1)
require.Equal(t, tc.expectedValue, cps[0].Settings.Get("token").MustString())
})
}
})
}
func TestContactPointServiceDecryptRedact(t *testing.T) {