diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index bfb6f1af0c1..c3fd9f99a7e 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -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" diff --git a/pkg/services/ngalert/accesscontrol.go b/pkg/services/ngalert/accesscontrol.go new file mode 100644 index 00000000000..1ded78e8d58 --- /dev/null +++ b/pkg/services/ngalert/accesscontrol.go @@ -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, + ) +} diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go index c1a3e163c74..2ca4b6fb535 100644 --- a/pkg/services/ngalert/api/authorization.go +++ b/pkg/services/ngalert/api/authorization.go @@ -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 diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 237e51d8cee..e8e419fbe23 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -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") +}