[release-11.6.9] Alerting: Fix contacts point issues (#115409)

* Alerting: Protect sensitive fields of contact points from unauthorized modification

- Introduce a new permission alert.notifications.receivers.protected:write. The permission is granted to contact point administrators.
- Introduce field Protected to NotifierOption
- Introduce DiffReport for models.Integrations with focus on Settings. The diff report is extended with methods that return all keys that are different between two settings.
- Add new annotation 'grafana.com/access/CanModifyProtected' to Receiver model
- Update receiver service to enforce the permission and return status 403 if unauthorized user modifies protected field
- Update legacy configuration post API and receiver testing API to enforce permission and return status 403 if unauthorized user modifies protected field.
- Update UI to disable protected fields if user cannot modify them

NOTE: the legacy configuration POST API now prohibits Editor role from modifying protected fields. After creating a new integration the protected fields (mostly URLs) effectively become read-only and can be changed by Admininstrators only.

Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>

* fix linter error

* prettier:write

---------

Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
Co-authored-by: Sonia Aguilar <soniaaguilarpeiron@gmail.com>
This commit is contained in:
Kevin Minehart
2025-12-16 15:27:11 +01:00
committed by GitHub
parent ebfd9e8378
commit 723a1642e8
45 changed files with 2094 additions and 275 deletions
@@ -552,3 +552,113 @@ func TestIntegrationAlertmanagerConfigurationPersistSecrets(t *testing.T) {
`, generatedUID), getBody(t, resp.Body))
}
}
func TestIntegrationAlertmanagerConfiguration_ProtectedFields(t *testing.T) {
testinfra.SQLiteIntegrationTest(t)
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
DisableLegacyAlerting: true,
EnableUnifiedAlerting: true,
DisableAnonymous: true,
AppModeProduction: true,
DisableFeatureToggles: []string{featuremgmt.FlagAlertingApiServer},
})
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleAdmin),
Password: "admin",
Login: "admin",
})
createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
DefaultOrgRole: string(org.RoleEditor),
Password: "editor",
Login: "editor",
})
adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
editorClient := newAlertingApiClient(grafanaListedAddr, "editor", "editor")
payload := `
{
"template_files": {},
"alertmanager_config": {
"route": {
"receiver": "webhook.receiver"
},
"receivers": [{
"name": "webhook.receiver",
"grafana_managed_receiver_configs": [{
"settings": {
"url": "http://localhost:9000"
},
"type": "webhook",
"name": "webhook.receiver",
"disableResolveMessage": false
}]
}]
}
}
`
var cfg apimodels.PostableUserConfig
err := json.Unmarshal([]byte(payload), &cfg)
require.NoError(t, err)
// create a new configuration that has protected fields, one is a secret
_, err = adminClient.PostConfiguration(t, cfg)
require.NoError(t, err)
patchUIDs := func(t *testing.T) {
t.Helper()
gCfg, _, _ := adminClient.GetAlertmanagerConfigWithStatus(t)
patched := 0
for i, gr := range gCfg.AlertmanagerConfig.GetReceivers() {
for j, gi := range gr.GrafanaManagedReceivers {
assert.Equal(t,
cfg.AlertmanagerConfig.Receivers[i].GrafanaManagedReceivers[j].Name,
gi.Name,
)
cfg.AlertmanagerConfig.Receivers[i].GrafanaManagedReceivers[j].UID = gi.UID
patched++
}
}
}
patchUIDs(t)
// Now check that editor can update non-protected fields and add new integrations to existing receivers
cfg.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[0].Settings = apimodels.RawMessage(`
{
"url":"http://localhost:9000",
"httpMethod": "PUT"
}
`)
cfg.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers = append(cfg.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers,
&apimodels.PostableGrafanaReceiver{
Name: cfg.AlertmanagerConfig.Receivers[0].Name,
Type: "webhook",
DisableResolveMessage: false,
Settings: apimodels.RawMessage(`{"url":"http://new-localhost:9000"}`),
SecureSettings: nil,
},
)
_, err = editorClient.PostConfiguration(t, cfg)
require.NoError(t, err)
patchUIDs(t)
// Now editor tries to update protected field and fails
cfg.AlertmanagerConfig.Receivers[0].GrafanaManagedReceivers[1].Settings = apimodels.RawMessage(`{"url":"http://very-localhost:9001"}`)
// Editor can add new integrations to existing receivers
success, err := editorClient.PostConfiguration(t, cfg)
require.Falsef(t, success, "the request should have failed")
t.Log(err)
require.Error(t, err)
// but Admin should still be able to update protected fields
_, err = adminClient.PostConfiguration(t, cfg)
require.NoError(t, err)
}