Alerting: Editor role can access all provisioning API (#85022)

This commit is contained in:
Yuri Tseretyan 2024-03-22 18:14:15 -04:00 committed by GitHub
parent 87548968e9
commit 48de8657c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 60 additions and 26 deletions

View File

@ -469,6 +469,8 @@ const (
ActionAlertingProvisioningRead = "alert.provisioning:read"
ActionAlertingProvisioningReadSecrets = "alert.provisioning.secrets:read"
ActionAlertingProvisioningWrite = "alert.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"
// Feature Management actions
ActionFeatureManagementRead = "featuremgmt.read"

View File

@ -205,6 +205,21 @@ var (
},
Grants: []string{string(org.RoleAdmin)},
}
alertingProvisioningStatus = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.provisioning.provenance:writer",
DisplayName: "Set provisioning status",
Description: "Set provisioning status for alerting resources. Should be used together with other regular roles (Notifications Writer and/or Rules Writer)",
Group: AlertRolesGroup,
Permissions: []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingProvisioningSetStatus, // organization scope
},
},
},
Grants: []string{string(org.RoleAdmin), string(org.RoleEditor)},
}
)
func DeclareFixedRoles(service accesscontrol.Service) error {
@ -212,6 +227,6 @@ func DeclareFixedRoles(service accesscontrol.Service) error {
rulesReaderRole, rulesWriterRole,
instancesReaderRole, instancesWriterRole,
notificationsReaderRole, notificationsWriterRole,
alertingReaderRole, alertingWriterRole, alertingProvisionerRole, alertingProvisioningReaderWithSecretsRole,
alertingReaderRole, alertingWriterRole, alertingProvisionerRole, alertingProvisioningReaderWithSecretsRole, alertingProvisioningStatus,
)
}

View File

@ -265,23 +265,33 @@ func (api *API) authorize(method, path string) web.Handler {
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningRead),
ac.EvalPermission(ac.ActionAlertingProvisioningReadSecrets),
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
)
// Grafana-only Provisioning Write Paths
case http.MethodPost + "/api/v1/provisioning/alert-rules":
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningWrite),
ac.EvalPermission(ac.ActionAlertingRuleCreate), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalAll(
ac.EvalPermission(ac.ActionAlertingRuleCreate), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
),
)
case http.MethodPut + "/api/v1/provisioning/alert-rules/{UID}":
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningWrite),
ac.EvalPermission(ac.ActionAlertingRuleUpdate), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalAll(
ac.EvalPermission(ac.ActionAlertingRuleUpdate), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
),
)
case http.MethodDelete + "/api/v1/provisioning/alert-rules/{UID}":
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningWrite),
ac.EvalPermission(ac.ActionAlertingRuleDelete), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalAll(
ac.EvalPermission(ac.ActionAlertingRuleDelete), // more granular permissions are enforced by the handler via "authorizeRuleChanges"
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
),
)
case http.MethodDelete + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(ac.Parameter(":FolderUID"))
@ -291,6 +301,7 @@ func (api *API) authorize(method, path string) web.Handler {
ac.EvalPermission(ac.ActionAlertingRuleDelete, scope),
ac.EvalPermission(ac.ActionAlertingRuleRead, scope),
ac.EvalPermission(dashboards.ActionFoldersRead, scope),
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
),
)
case http.MethodPut + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
@ -300,6 +311,7 @@ func (api *API) authorize(method, path string) web.Handler {
ac.EvalAll(
ac.EvalPermission(ac.ActionAlertingRuleRead, scope),
ac.EvalPermission(dashboards.ActionFoldersRead, scope),
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
ac.EvalAny( // the exact permissions will be checked after the operations are determined
ac.EvalPermission(ac.ActionAlertingRuleUpdate, scope),
ac.EvalPermission(ac.ActionAlertingRuleCreate, scope),
@ -318,7 +330,13 @@ func (api *API) authorize(method, path string) web.Handler {
http.MethodPost + "/api/v1/provisioning/mute-timings",
http.MethodPut + "/api/v1/provisioning/mute-timings/{name}",
http.MethodDelete + "/api/v1/provisioning/mute-timings/{name}":
eval = ac.EvalPermission(ac.ActionAlertingProvisioningWrite) // organization scope
eval = ac.EvalAny(
ac.EvalPermission(ac.ActionAlertingProvisioningWrite), // organization scope,
ac.EvalAll(
ac.EvalPermission(ac.ActionAlertingNotificationsWrite),
ac.EvalPermission(ac.ActionAlertingProvisioningSetStatus),
),
)
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))

View File

@ -98,24 +98,24 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 401, resp.StatusCode)
})
t.Run("viewer GET should 403", func(t *testing.T) {
t.Run("viewer GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "viewer", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("editor GET should 403", func(t *testing.T) {
t.Run("editor GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "editor", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("admin GET should succeed", func(t *testing.T) {
@ -148,14 +148,13 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 403, resp.StatusCode)
})
t.Run("editor PUT should 403", func(t *testing.T) {
t.Run("editor PUT should succeed", func(t *testing.T) {
req := createTestRequest("PUT", url, "editor", body)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 202, resp.StatusCode)
})
t.Run("admin PUT should succeed", func(t *testing.T) {
@ -190,24 +189,24 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 401, resp.StatusCode)
})
t.Run("viewer GET should 403", func(t *testing.T) {
t.Run("viewer GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "viewer", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("editor GET should 403", func(t *testing.T) {
t.Run("editor GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "editor", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("admin GET should succeed", func(t *testing.T) {
@ -240,14 +239,14 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 403, resp.StatusCode)
})
t.Run("editor POST should 403", func(t *testing.T) {
t.Run("editor POST should succeed", func(t *testing.T) {
req := createTestRequest("POST", url, "editor", body)
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 202, resp.StatusCode)
})
t.Run("admin POST should succeed", func(t *testing.T) {
@ -274,24 +273,24 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 401, resp.StatusCode)
})
t.Run("viewer GET should 403", func(t *testing.T) {
t.Run("viewer GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "viewer", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("editor GET should 403", func(t *testing.T) {
t.Run("editor GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "editor", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("admin GET should succeed", func(t *testing.T) {
@ -318,24 +317,24 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, 401, resp.StatusCode)
})
t.Run("viewer GET should 403", func(t *testing.T) {
t.Run("viewer GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "viewer", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("editor GET should 403", func(t *testing.T) {
t.Run("editor GET should succeed", func(t *testing.T) {
req := createTestRequest("GET", url, "editor", "")
resp, err := http.DefaultClient.Do(req)
require.NoError(t, err)
require.NoError(t, resp.Body.Close())
require.Equal(t, 403, resp.StatusCode)
require.Equal(t, 200, resp.StatusCode)
})
t.Run("admin GET should succeed", func(t *testing.T) {