mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
e231211234
commit
3952f627eb
@ -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) {
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user