Alerting: fixed roles for fine-grained access control (#46553)

* move alerting actions to accesscontrol to avoid cycledeps
* define new actions and fixed roles for alerting
* add folder permission to alert reader role
This commit is contained in:
Yuriy Tseretyan 2022-03-15 14:30:32 -04:00 committed by GitHub
parent 00c93fff8c
commit 468def0c00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 231 additions and 136 deletions

View File

@ -344,16 +344,44 @@ const (
// Dashboard scopes
ScopeDashboardsAll = "dashboards:*"
// Alert scopes are divided into two groups. The internal (to Grafana) and the external ones.
// For the Grafana ones, given we have ACID control we're able to provide better granularity by defining CRUD options.
// For the external ones, we only have read and write permissions due to the lack of atomicity control of the external system.
// Alerting rules actions
ActionAlertingRuleCreate = "alert.rules:create"
ActionAlertingRuleRead = "alert.rules:read"
ActionAlertingRuleUpdate = "alert.rules:update"
ActionAlertingRuleDelete = "alert.rules:delete"
// Alerting instances (+silences) actions
ActionAlertingInstanceCreate = "alert.instances:create"
ActionAlertingInstanceUpdate = "alert.instances:update"
ActionAlertingInstanceRead = "alert.instances:read"
// Alerting Notification policies actions
ActionAlertingNotificationsCreate = "alert.notifications:create"
ActionAlertingNotificationsRead = "alert.notifications:read"
ActionAlertingNotificationsUpdate = "alert.notifications:update"
ActionAlertingNotificationsDelete = "alert.notifications:delete"
// External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
ActionAlertingRuleExternalWrite = "alert.rules.external:write"
ActionAlertingRuleExternalRead = "alert.rules.external:read"
// External alerting instances actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
ActionAlertingInstancesExternalWrite = "alert.instances.external:write"
ActionAlertingInstancesExternalRead = "alert.instances.external:read"
// External alerting notifications actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
ActionAlertingNotificationsExternalWrite = "alert.notifications.external:write"
ActionAlertingNotificationsExternalRead = "alert.notifications.external:read"
)
var (
// Team scope
ScopeTeamsID = Scope("teams", "id", Parameter(":teamId"))
// Folder scopes
// Datasource scopes
)
const RoleGrafanaAdmin = "Grafana Admin"

View File

@ -0,0 +1,188 @@
package ngalert
import (
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
)
const AlertRolesGroup = "Alerting"
var (
rulesReaderRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.rules:reader",
DisplayName: "Rules Reader",
Description: "Can read alert rules in all Grafana folders and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingRuleRead,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: dashboards.ActionFoldersRead,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: accesscontrol.ActionAlertingRuleExternalRead,
Scope: datasources.ScopeDatasourcesAll,
},
},
},
}
rulesEditorRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.rules:editor",
DisplayName: "Rules Editor",
Description: "Can add, update, and delete rules in any Grafana folder and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: accesscontrol.ConcatPermissions(rulesReaderRole.Role.Permissions, []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingRuleCreate,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: accesscontrol.ActionAlertingRuleUpdate,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: accesscontrol.ActionAlertingRuleDelete,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: accesscontrol.ActionAlertingRuleExternalWrite,
Scope: datasources.ScopeDatasourcesAll,
},
}),
},
Grants: []string{string(models.ROLE_EDITOR)},
}
instancesReaderRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.instances:reader",
DisplayName: "Instances and Silences Reader",
Description: "Can read instances and silences of Grafana and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingInstanceRead,
Scope: dashboards.ScopeFoldersAll,
},
{
Action: accesscontrol.ActionAlertingInstancesExternalRead,
Scope: datasources.ScopeDatasourcesAll,
},
},
},
Grants: []string{string(models.ROLE_VIEWER)},
}
instancesEditorRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.instances:editor",
DisplayName: "Silences Editor",
Description: "Can add and update silences in Grafana and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: accesscontrol.ConcatPermissions(instancesReaderRole.Role.Permissions, []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingInstanceCreate,
},
{
Action: accesscontrol.ActionAlertingInstanceUpdate,
},
{
Action: accesscontrol.ActionAlertingInstancesExternalWrite,
Scope: datasources.ScopeDatasourcesAll,
},
}),
},
Grants: []string{string(models.ROLE_EDITOR)},
}
notificationsReaderRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.notifications:reader",
DisplayName: "Notifications Reader",
Description: "Can read notification policies and contact points in Grafana and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingNotificationsRead,
},
{
Action: accesscontrol.ActionAlertingNotificationsExternalRead,
Scope: datasources.ScopeDatasourcesAll,
},
},
},
Grants: []string{string(models.ROLE_VIEWER)},
}
notificationsEditorRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting.notifications:editor",
DisplayName: "Notifications Editor",
Description: "Can add, update, and delete contact points and notification policies in Grafana and external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: accesscontrol.ConcatPermissions(notificationsReaderRole.Role.Permissions, []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingNotificationsCreate,
},
{
Action: accesscontrol.ActionAlertingNotificationsUpdate,
},
{
Action: accesscontrol.ActionAlertingNotificationsDelete,
},
{
Action: accesscontrol.ActionAlertingNotificationsExternalWrite,
Scope: datasources.ScopeDatasourcesAll,
},
}),
},
Grants: []string{string(models.ROLE_EDITOR)},
}
alertingReaderRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting:reader",
DisplayName: "Full read-only access",
Description: "Can read alert rules, instances, silences, contact points, and notification policies in Grafana and all external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: accesscontrol.ConcatPermissions(rulesReaderRole.Role.Permissions, instancesReaderRole.Role.Permissions, notificationsReaderRole.Role.Permissions),
},
Grants: []string{string(models.ROLE_VIEWER)},
}
alertingWriterRole = accesscontrol.RoleRegistration{
Role: accesscontrol.RoleDTO{
Name: accesscontrol.FixedRolePrefix + "alerting:editor",
DisplayName: "Full access",
Description: "Can add,update and delete alert rules, instances, silences, contact points, and notification policies in Grafana and all external providers",
Group: AlertRolesGroup,
Version: 1,
Permissions: accesscontrol.ConcatPermissions(rulesEditorRole.Role.Permissions, instancesEditorRole.Role.Permissions, notificationsEditorRole.Role.Permissions),
},
Grants: []string{string(models.ROLE_EDITOR)},
}
)
func DeclareFixedRoles(ac accesscontrol.AccessControl) error {
return ac.DeclareFixedRoles(
rulesReaderRole, rulesEditorRole,
instancesReaderRole, instancesEditorRole,
notificationsReaderRole, notificationsEditorRole,
alertingReaderRole, alertingWriterRole,
)
}

View File

@ -2,139 +2,9 @@ package api
import (
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/web"
)
var (
// Namespaces (aka folder) scopes
ScopeNamespace = "namespaces"
ScopeNamespaceAll = ac.GetResourceAllScope(ScopeNamespace)
ScopeNamespaceName = ac.Scope(ScopeNamespace, "title", ac.Parameter(":Namespace"))
ScopeDatasource = "datasources"
ScopeDatasourcesAll = ac.GetResourceAllScope(ScopeDatasource)
ScopeDatasourceID = ac.Scope(ScopeDatasource, "id", ac.Parameter(":Recipient"))
// Alerting rules actions
ActionAlertingRuleCreate = "alert.rules:create"
ActionAlertingRuleRead = "alert.rules:read"
ActionAlertingRuleUpdate = "alert.rules:update"
ActionAlertingRuleDelete = "alert.rules:delete"
// Alerting instances (+silences) actions
ActionAlertingInstanceCreate = "alert.instances:create"
ActionAlertingInstanceUpdate = "alert.instances:update"
ActionAlertingInstanceRead = "alert.instances:read"
// Alerting Notification policies actions
ActionAlertingNotificationsCreate = "alert.notifications:create"
ActionAlertingNotificationsRead = "alert.notifications:read"
ActionAlertingNotificationsUpdate = "alert.notifications:update"
ActionAlertingNotificationsDelete = "alert.notifications:delete"
)
var (
alertingReader = ac.FixedRolePrefix + "alerting:reader"
alertingWriter = ac.FixedRolePrefix + "alerting:writer"
alertingReaderRole = ac.RoleRegistration{
Role: ac.RoleDTO{
Name: alertingReader,
DisplayName: "Alerting Rules Reader",
Description: "Read alerting rules",
Group: "Alerting",
Version: 1,
Permissions: []ac.Permission{
{
Action: ActionAlertingRuleRead,
Scope: ScopeNamespaceAll,
},
{
Action: ActionAlertingRuleRead,
Scope: ScopeDatasourcesAll,
},
{
Action: ActionAlertingInstanceRead, // scope is the current organization
},
{
Action: ActionAlertingNotificationsRead, // scope is the current organization
},
},
},
Grants: []string{string(models.ROLE_VIEWER)},
}
alertingWriterRole = ac.RoleRegistration{
Role: ac.RoleDTO{
Name: alertingWriter,
DisplayName: "Alerting Rules Writer",
Description: "Read and update alerting rules",
Group: "Alerting",
Version: 1,
Permissions: ac.ConcatPermissions(alertingReaderRole.Role.Permissions, []ac.Permission{
{
Action: ActionAlertingRuleCreate,
Scope: ScopeNamespaceAll,
},
{
Action: ActionAlertingRuleUpdate,
Scope: ScopeNamespaceAll,
},
{
Action: ActionAlertingRuleDelete,
Scope: ScopeNamespaceAll,
},
{
Action: ActionAlertingRuleCreate,
Scope: ScopeDatasourcesAll,
},
{
Action: ActionAlertingRuleUpdate,
Scope: ScopeDatasourcesAll,
},
{
Action: ActionAlertingRuleDelete,
Scope: ScopeDatasourcesAll,
},
{
Action: ActionAlertingInstanceCreate, // scope is the current organization
},
{
Action: ActionAlertingInstanceUpdate, // scope is the current organization
},
{
Action: ActionAlertingNotificationsCreate, // scope is the current organization
},
{
Action: ActionAlertingNotificationsUpdate, // scope is the current organization
},
{
Action: ActionAlertingNotificationsDelete, // scope is the current organization
},
}),
},
Grants: []string{string(models.ROLE_EDITOR)},
}
)
// TODO temporary
func (api *API) isFgacDisabled() bool {
return api.Cfg.IsFeatureToggleEnabled == nil || !api.Cfg.IsFeatureToggleEnabled("alerting_fgac")
}
// DeclareFixedRoles registers the fixed roles provided by the alerting module
func (api *API) DeclareFixedRoles() error {
// TODO temporary
if api.isFgacDisabled() {
return nil
}
return api.AccessControl.DeclareFixedRoles(
alertingReaderRole, alertingWriterRole,
)
}
func (api *API) authorize(method, path string) web.Handler {
// TODO Add fine-grained authorization for every route
return middleware.ReqSignedIn

View File

@ -155,7 +155,10 @@ func (ng *AlertNG) init() error {
}
api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
return api.DeclareFixedRoles()
if ng.isFgacDisabled() {
return nil
}
return DeclareFixedRoles(ng.accesscontrol)
}
// Run starts the scheduler and Alertmanager.
@ -183,3 +186,9 @@ func (ng *AlertNG) IsDisabled() bool {
}
return !ng.Cfg.UnifiedAlerting.IsEnabled()
}
// TODO temporary. Remove after https://github.com/grafana/grafana/pull/46358 is merged
// isFgacDisabled returns true if fine-grained access for Alerting is enabled.
func (ng *AlertNG) isFgacDisabled() bool {
return ng.Cfg.IsFeatureToggleEnabled == nil || !ng.Cfg.IsFeatureToggleEnabled("alerting_fgac")
}