mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: declare authorization actions, scopes and roles for fine-grained access (#45748)
* add actions, roles and route mapping for rule permission * add instance\notification actions * do not declare alerting roles if no feature flag is set (temporary)
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
@@ -70,6 +71,7 @@ type API struct {
|
||||
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
|
||||
StateManager *state.Manager
|
||||
SecretsService secrets.Service
|
||||
AccessControl accesscontrol.AccessControl
|
||||
}
|
||||
|
||||
// RegisterAPIEndpoints registers API handlers
|
||||
|
||||
@@ -2,9 +2,139 @@ 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
|
||||
|
||||
@@ -5,10 +5,13 @@ import (
|
||||
"net/url"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@@ -24,12 +27,12 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService, routeRegister routing.RouteRegister,
|
||||
sqlStore *sqlstore.SQLStore, kvStore kvstore.KVStore, expressionService *expr.Service, dataProxy *datasourceproxy.DataSourceProxyService,
|
||||
quotaService *quota.QuotaService, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert, folderService dashboards.FolderService) (*AlertNG, error) {
|
||||
quotaService *quota.QuotaService, secretsService secrets.Service, notificationService notifications.Service, m *metrics.NGAlert,
|
||||
folderService dashboards.FolderService, ac accesscontrol.AccessControl) (*AlertNG, error) {
|
||||
ng := &AlertNG{
|
||||
Cfg: cfg,
|
||||
DataSourceCache: dataSourceCache,
|
||||
@@ -44,6 +47,7 @@ func ProvideService(cfg *setting.Cfg, dataSourceCache datasources.CacheService,
|
||||
Log: log.New("ngalert"),
|
||||
NotificationService: notificationService,
|
||||
folderService: folderService,
|
||||
accesscontrol: ac,
|
||||
}
|
||||
|
||||
if ng.IsDisabled() {
|
||||
@@ -77,6 +81,7 @@ type AlertNG struct {
|
||||
|
||||
// Alerting notification services
|
||||
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
|
||||
accesscontrol accesscontrol.AccessControl
|
||||
}
|
||||
|
||||
func (ng *AlertNG) init() error {
|
||||
@@ -145,10 +150,11 @@ func (ng *AlertNG) init() error {
|
||||
AdminConfigStore: store,
|
||||
MultiOrgAlertmanager: ng.MultiOrgAlertmanager,
|
||||
StateManager: ng.stateManager,
|
||||
AccessControl: ng.accesscontrol,
|
||||
}
|
||||
api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
||||
|
||||
return nil
|
||||
return api.DeclareFixedRoles()
|
||||
}
|
||||
|
||||
// Run starts the scheduler and Alertmanager.
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
databasestore "github.com/grafana/grafana/pkg/services/dashboards/database"
|
||||
dashboardservice "github.com/grafana/grafana/pkg/services/dashboards/manager"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
@@ -42,9 +43,10 @@ func SetupTestEnv(t *testing.T, baseInterval time.Duration) (*ngalert.AlertNG, *
|
||||
secretsService := secretsManager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||
dashboardStore := databasestore.ProvideDashboardStore(sqlStore)
|
||||
folderService := dashboardservice.ProvideFolderService(dashboardservice.ProvideDashboardService(dashboardStore, nil), dashboardStore, nil)
|
||||
ac := mock.New()
|
||||
ng, err := ngalert.ProvideService(
|
||||
cfg, nil, routing.NewRouteRegister(), sqlStore,
|
||||
nil, nil, nil, nil, secretsService, nil, m, folderService,
|
||||
nil, nil, nil, nil, secretsService, nil, m, folderService, ac,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
return ng, &store.DBstore{
|
||||
|
||||
Reference in New Issue
Block a user