Alerting: Remove option to return settings from api/v1/receivers and restrict provisioning action access (#90861)

* Remove provisioning action access to v1/receivers api

* Separate ListOnly functionality to its own method without decryption
This commit is contained in:
Matthew Jacobson
2024-08-05 11:49:23 -04:00
committed by GitHub
parent 4d23382497
commit 53cfdf0ef8
8 changed files with 145 additions and 105 deletions
@@ -120,7 +120,7 @@ func TestRouteGetReceivers(t *testing.T) {
},
},
}
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
fakeReceiverSvc.ListReceiversFn = func(ctx context.Context, q models.ListReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
return expected, nil
}
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
@@ -134,7 +134,7 @@ func TestRouteGetReceivers(t *testing.T) {
})
t.Run("builds query from request context", func(t *testing.T) {
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
fakeReceiverSvc.ListReceiversFn = func(ctx context.Context, q models.ListReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
return []definitions.GettableApiReceiver{}, nil
}
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
@@ -148,19 +148,18 @@ func TestRouteGetReceivers(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Status())
call := fakeReceiverSvc.PopMethodCall()
require.Equal(t, "GetReceivers", call.Method)
expectedQ := models.GetReceiversQuery{
Names: []string{"receiver1", "receiver2"},
Limit: 1,
Offset: 2,
Decrypt: true,
OrgID: 1,
require.Equal(t, "ListReceivers", call.Method)
expectedQ := models.ListReceiversQuery{
Names: []string{"receiver1", "receiver2"},
Limit: 1,
Offset: 2,
OrgID: 1,
}
require.Equal(t, expectedQ, call.Args[1])
})
t.Run("should pass along permission denied response", func(t *testing.T) {
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
fakeReceiverSvc.ListReceiversFn = func(ctx context.Context, q models.ListReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
return nil, ac.ErrAuthorizationBase.Errorf("")
}
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
@@ -197,33 +196,9 @@ func TestRouteGetReceiversResponses(t *testing.T) {
require.Equal(t, 200, response.Status())
})
t.Run("decrypt true without alert.provisioning.secrets:read permissions returns 403", func(t *testing.T) {
recPermCheck := false
env := createTestEnv(t, testConfig)
env.ac = &recordingAccessControlFake{
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversReadSecrets) {
recPermCheck = true
}
return false, nil
},
}
sut := createNotificationSrvSutFromEnv(t, &env)
rc := createTestRequestCtx()
rc.Context.Req.Form.Set("decrypt", "true")
response := sut.RouteGetReceivers(&rc)
require.True(t, recPermCheck)
require.Equal(t, 403, response.Status())
})
t.Run("json body content is as expected", func(t *testing.T) {
expectedDecryptedResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureFields":{}}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"testpass","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"settings":{"client":"some client","integrationKey":"some key","severity":"criticalish"},"secureFields":{"integrationKey":true}}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":true,"settings":{"text":"title body test","title":"title test","url":"some secure slack webhook"},"secureFields":{"url":true}}]}]`
expectedRedactedResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureFields":{}}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"[REDACTED]","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"settings":{"client":"some client","integrationKey":"[REDACTED]","severity":"criticalish"},"secureFields":{"integrationKey":true}}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":true,"settings":{"text":"title body test","title":"title test","url":"[REDACTED]"},"secureFields":{"url":true}}]}]`
expectedListResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"secureFields":null}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":false,"secureFields":null},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"secureFields":null}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"secureFields":null}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":false,"secureFields":null}]}]`
expectedListResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"secureFields":{}}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":false,"secureFields":{}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"secureFields":{}}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"secureFields":{}}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":false,"secureFields":{}}]}]`
t.Run("limit offset", func(t *testing.T) {
env := createTestEnv(t, testContactPointConfig)
sut := createNotificationSrvSutFromEnv(t, &env)
@@ -233,7 +208,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
rc.Context.Req.Form.Set("decrypt", "false")
var expected []definitions.GettableApiReceiver
err := json.Unmarshal([]byte(expectedRedactedResponse), &expected)
err := json.Unmarshal([]byte(expectedListResponse), &expected)
require.NoError(t, err)
type testcase struct {
limit int
@@ -266,7 +241,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
})
}
})
t.Run("decrypt false with read permissions is redacted", func(t *testing.T) {
t.Run("decrypt false with read permissions, does not have settings", func(t *testing.T) {
env := createTestEnv(t, testContactPointConfig)
sut := createNotificationSrvSutFromEnv(t, &env)
rc := createTestRequestCtx()
@@ -277,7 +252,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
response := sut.RouteGetReceivers(&rc)
require.Equal(t, 200, response.Status())
require.Equal(t, expectedRedactedResponse, string(response.Body())) // TODO: Should this endpoint ever return settings?
require.Equal(t, expectedListResponse, string(response.Body()))
})
t.Run("decrypt false with only list permissions, does not have settings", func(t *testing.T) {
env := createTestEnv(t, testContactPointConfig)
@@ -300,7 +275,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
require.Equal(t, 200, response.Status())
require.Equal(t, expectedListResponse, string(response.Body()))
})
t.Run("decrypt true with all permissions, contains decrypted settings", func(t *testing.T) {
t.Run("decrypt true with all permissions, does not have settings", func(t *testing.T) {
env := createTestEnv(t, testContactPointConfig)
env.ac = &recordingAccessControlFake{
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
@@ -316,7 +291,7 @@ func TestRouteGetReceiversResponses(t *testing.T) {
response := sut.RouteGetReceivers(&rc)
require.Equal(t, 200, response.Status())
require.Equal(t, expectedDecryptedResponse, string(response.Body())) // TODO: Should this endpoint ever return settings?
require.Equal(t, expectedListResponse, string(response.Body()))
})
})
})