From 356a29592be6c5fe5de81138f6a55e03a8cdfac2 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 9 May 2024 13:19:07 -0400 Subject: [PATCH] Alerting: Add two sets of provisioning actions for rules and notifications (#87149) --- pkg/services/accesscontrol/models.go | 11 +++++--- pkg/services/ngalert/accesscontrol.go | 18 +++++++++++++ pkg/services/ngalert/api/authorization.go | 26 +++++++++++++++---- .../ngalert/provisioning/accesscontrol.go | 6 ++++- .../provisioning/accesscontrol_test.go | 13 ++++++++-- 5 files changed, 63 insertions(+), 11 deletions(-) diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index 0b5caa99a9c..d01f64dfeef 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -470,9 +470,14 @@ const ( ActionAlertingNotificationsExternalRead = "alert.notifications.external:read" // Alerting provisioning actions - ActionAlertingProvisioningRead = "alert.provisioning:read" - ActionAlertingProvisioningReadSecrets = "alert.provisioning.secrets:read" - ActionAlertingProvisioningWrite = "alert.provisioning:write" + ActionAlertingProvisioningRead = "alert.provisioning:read" + ActionAlertingProvisioningReadSecrets = "alert.provisioning.secrets:read" + ActionAlertingProvisioningWrite = "alert.provisioning:write" + ActionAlertingRulesProvisioningRead = "alert.rules.provisioning:read" + ActionAlertingRulesProvisioningWrite = "alert.rules.provisioning:write" + ActionAlertingNotificationsProvisioningRead = "alert.notifications.provisioning:read" + ActionAlertingNotificationsProvisioningWrite = "alert.notifications.provisioning:write" + // ActionAlertingProvisioningSetStatus Gives access to set provisioning status to alerting resources. Cannot be used alone. Only in conjunction with other permissions. ActionAlertingProvisioningSetStatus = "alert.provisioning.provenance:write" diff --git a/pkg/services/ngalert/accesscontrol.go b/pkg/services/ngalert/accesscontrol.go index 780c94d997a..84b981fd2ab 100644 --- a/pkg/services/ngalert/accesscontrol.go +++ b/pkg/services/ngalert/accesscontrol.go @@ -191,6 +191,18 @@ var ( { Action: accesscontrol.ActionAlertingProvisioningWrite, // organization scope }, + { + Action: accesscontrol.ActionAlertingRulesProvisioningRead, // organization scope + }, + { + Action: accesscontrol.ActionAlertingRulesProvisioningWrite, // organization scope + }, + { + Action: accesscontrol.ActionAlertingNotificationsProvisioningRead, // organization scope + }, + { + Action: accesscontrol.ActionAlertingNotificationsProvisioningWrite, // organization scope + }, { Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll, @@ -213,6 +225,12 @@ var ( { Action: accesscontrol.ActionAlertingProvisioningRead, // organization scope }, + { + Action: accesscontrol.ActionAlertingRulesProvisioningRead, // organization scope + }, + { + Action: accesscontrol.ActionAlertingNotificationsProvisioningRead, // organization scope + }, }, }, Grants: []string{string(org.RoleAdmin)}, diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index 81c5252f501..dd03da7ca4c 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -249,15 +249,17 @@ func (api *API) authorize(method, path string) web.Handler { http.MethodGet + "/api/v1/provisioning/mute-timings/export", http.MethodGet + "/api/v1/provisioning/mute-timings/{name}/export": eval = ac.EvalAny( - ac.EvalPermission(ac.ActionAlertingNotificationsRead), // organization scope - ac.EvalPermission(ac.ActionAlertingProvisioningRead), // organization scope - ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), // organization scope + ac.EvalPermission(ac.ActionAlertingNotificationsRead), // organization scope + ac.EvalPermission(ac.ActionAlertingProvisioningRead), // organization scope + ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningRead), // organization scope + ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), // organization scope ) case http.MethodGet + "/api/v1/provisioning/alert-rules", http.MethodGet + "/api/v1/provisioning/alert-rules/export": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningRead), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningRead), ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), ac.EvalAll( // scopes are enforced in the handler ac.EvalPermission(ac.ActionAlertingRuleRead), @@ -268,6 +270,7 @@ func (api *API) authorize(method, path string) web.Handler { http.MethodGet + "/api/v1/provisioning/alert-rules/{UID}/export": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningRead), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningRead), ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleRead), @@ -280,6 +283,7 @@ func (api *API) authorize(method, path string) web.Handler { scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.Parameter(":FolderUID")) eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningRead), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningRead), ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleRead, scope), @@ -295,6 +299,7 @@ func (api *API) authorize(method, path string) web.Handler { http.MethodGet + "/api/v1/provisioning/mute-timings/{name}": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningRead), + ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningRead), // organization scope ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), ac.EvalPermission(ac.ActionAlertingNotificationsRead), ) @@ -303,6 +308,7 @@ func (api *API) authorize(method, path string) web.Handler { case http.MethodPost + "/api/v1/provisioning/alert-rules": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleCreate), // more granular permissions are enforced by the handler via "authorizeRuleChanges" ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), @@ -311,6 +317,7 @@ func (api *API) authorize(method, path string) web.Handler { case http.MethodPut + "/api/v1/provisioning/alert-rules/{UID}": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleUpdate), // more granular permissions are enforced by the handler via "authorizeRuleChanges" ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), @@ -319,6 +326,7 @@ func (api *API) authorize(method, path string) web.Handler { case http.MethodDelete + "/api/v1/provisioning/alert-rules/{UID}": eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleDelete), // more granular permissions are enforced by the handler via "authorizeRuleChanges" ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), @@ -328,6 +336,7 @@ func (api *API) authorize(method, path string) web.Handler { scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.Parameter(":FolderUID")) eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleDelete, scope), ac.EvalPermission(ac.ActionAlertingRuleRead, scope), @@ -339,6 +348,7 @@ func (api *API) authorize(method, path string) web.Handler { scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.Parameter(":FolderUID")) eval = ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), ac.EvalAll( ac.EvalPermission(ac.ActionAlertingRuleRead, scope), ac.EvalPermission(dashboards.ActionFoldersRead, scope), @@ -362,7 +372,8 @@ func (api *API) authorize(method, path string) web.Handler { http.MethodPut + "/api/v1/provisioning/mute-timings/{name}", http.MethodDelete + "/api/v1/provisioning/mute-timings/{name}": eval = ac.EvalAny( - ac.EvalPermission(ac.ActionAlertingProvisioningWrite), // organization scope, + ac.EvalPermission(ac.ActionAlertingProvisioningWrite), // organization scope, + ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningWrite), // organization scope ac.EvalAll( ac.EvalPermission(ac.ActionAlertingNotificationsWrite), ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus), @@ -370,7 +381,12 @@ func (api *API) authorize(method, path string) web.Handler { ) case http.MethodGet + "/api/v1/notifications/time-intervals/{name}", http.MethodGet + "/api/v1/notifications/time-intervals": - eval = ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsRead), ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsRead), ac.EvalPermission(ac.ActionAlertingProvisioningRead)) + eval = ac.EvalAny( + ac.EvalPermission(ac.ActionAlertingNotificationsRead), + ac.EvalPermission(ac.ActionAlertingNotificationsTimeIntervalsRead), + ac.EvalPermission(ac.ActionAlertingProvisioningRead), + ac.EvalPermission(ac.ActionAlertingNotificationsProvisioningRead), // organization scope + ) } if eval != nil { diff --git a/pkg/services/ngalert/provisioning/accesscontrol.go b/pkg/services/ngalert/provisioning/accesscontrol.go index 91567e6b1f3..33305fec79a 100644 --- a/pkg/services/ngalert/provisioning/accesscontrol.go +++ b/pkg/services/ngalert/provisioning/accesscontrol.go @@ -64,6 +64,7 @@ func (p *provisioningRuleAccessControl) CanReadAllRules(ctx context.Context, use return p.HasAccess(ctx, user, ac.EvalAny( ac.EvalPermission(ac.ActionAlertingProvisioningRead), ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningRead), )) } @@ -72,5 +73,8 @@ func (p *provisioningRuleAccessControl) CanReadAllRules(ctx context.Context, use // It returns true if the user has permission, false otherwise. // It returns an error if there is a problem checking the permission. func (p *provisioningRuleAccessControl) CanWriteAllRules(ctx context.Context, user identity.Requester) (bool, error) { - return p.HasAccess(ctx, user, ac.EvalPermission(ac.ActionAlertingProvisioningWrite)) + return p.HasAccess(ctx, user, ac.EvalAny( + ac.EvalPermission(ac.ActionAlertingProvisioningWrite), + ac.EvalPermission(ac.ActionAlertingRulesProvisioningWrite), + )) } diff --git a/pkg/services/ngalert/provisioning/accesscontrol_test.go b/pkg/services/ngalert/provisioning/accesscontrol_test.go index a55e44429b2..c5f678e65a3 100644 --- a/pkg/services/ngalert/provisioning/accesscontrol_test.go +++ b/pkg/services/ngalert/provisioning/accesscontrol_test.go @@ -36,6 +36,7 @@ func TestCanReadAllRules(t *testing.T) { require.Equal(t, accesscontrol.EvalAny( accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningRead), accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingRulesProvisioningRead), ).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) }) @@ -67,7 +68,11 @@ func TestCanWriteAllRules(t *testing.T) { require.Len(t, rs.Calls, 1) require.Equal(t, "HasAccess", rs.Calls[0].MethodName) - require.Equal(t, accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningWrite).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) + require.Equal(t, + accesscontrol.EvalAny( + accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningWrite), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingRulesProvisioningWrite), + ).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) }) t.Run("should return error", func(t *testing.T) { @@ -104,6 +109,7 @@ func TestAuthorizeAccessToRuleGroup(t *testing.T) { assert.Equal(t, accesscontrol.EvalAny( accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningRead), accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingRulesProvisioningRead), ).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) assert.Equal(t, testUser, rs.Calls[0].Arguments[1]) }) @@ -176,7 +182,10 @@ func TestAuthorizeRuleChanges(t *testing.T) { require.Len(t, rs.Calls, 1) require.Equal(t, "HasAccess", rs.Calls[0].MethodName) - assert.Equal(t, accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningWrite).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) + assert.Equal(t, accesscontrol.EvalAny( + accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningWrite), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingRulesProvisioningWrite), + ).GoString(), rs.Calls[0].Arguments[2].(accesscontrol.Evaluator).GoString()) assert.Equal(t, testUser, rs.Calls[0].Arguments[1]) })