Alerting: Fix RBAC actions for notification policies (#49185)

* squash actions "alert.notifications:update", "alert.notifications:create", "alert.notifications:delete" to "alert.notifications:write"
* add migration
* update UI to use the write action
* update docs
* changelog
This commit is contained in:
Yuriy Tseretyan
2022-05-20 10:55:07 -04:00
committed by GitHub
parent 2780651ea8
commit 258b3ab18b
12 changed files with 111 additions and 36 deletions

View File

@@ -372,10 +372,8 @@ const (
ActionAlertingInstanceRead = "alert.instances:read"
// Alerting Notification policies actions
ActionAlertingNotificationsCreate = "alert.notifications:create"
ActionAlertingNotificationsRead = "alert.notifications:read"
ActionAlertingNotificationsUpdate = "alert.notifications:update"
ActionAlertingNotificationsDelete = "alert.notifications:delete"
ActionAlertingNotificationsRead = "alert.notifications:read"
ActionAlertingNotificationsWrite = "alert.notifications:write"
// 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"

View File

@@ -56,6 +56,7 @@ Scopes must have an order to ensure consistency and ease of search, this helps u
- [FEATURE] Indicate whether alert rule is provisioned when GETting the rule #48458
- [BUGFIX] Migration: ignore alerts that do not belong to any existing organization\dashboard #49192
- [BUGFIX] Allow anonymous access to alerts #49203
- [BUGFIX] RBAC: replace create\update\delete actions for notification policies by alert.notifications:write #49185
## 8.5.3

View File

@@ -125,16 +125,10 @@ var (
DisplayName: "Notifications Editor",
Description: "Can add, update, and delete contact points and notification policies in Grafana and external providers",
Group: AlertRolesGroup,
Version: 1,
Version: 2,
Permissions: accesscontrol.ConcatPermissions(notificationsReaderRole.Role.Permissions, []accesscontrol.Permission{
{
Action: accesscontrol.ActionAlertingNotificationsCreate,
},
{
Action: accesscontrol.ActionAlertingNotificationsUpdate,
},
{
Action: accesscontrol.ActionAlertingNotificationsDelete,
Action: accesscontrol.ActionAlertingNotificationsWrite,
},
{
Action: accesscontrol.ActionAlertingNotificationsExternalWrite,
@@ -162,7 +156,7 @@ var (
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: 2,
Version: 3,
Permissions: accesscontrol.ConcatPermissions(rulesEditorRole.Role.Permissions, instancesEditorRole.Role.Permissions, notificationsEditorRole.Role.Permissions),
},
Grants: []string{string(models.ROLE_EDITOR), string(models.ROLE_ADMIN)},

View File

@@ -145,7 +145,7 @@ func (api *API) authorize(method, path string) web.Handler {
// Grafana Paths
case http.MethodDelete + "/api/alertmanager/grafana/config/api/v1/alerts": // reset alertmanager config to the default
eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete)
eval = ac.EvalPermission(ac.ActionAlertingNotificationsWrite)
case http.MethodGet + "/api/alertmanager/grafana/config/api/v1/alerts":
fallback = middleware.ReqEditorRole
eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)
@@ -153,14 +153,14 @@ func (api *API) authorize(method, path string) web.Handler {
eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)
case http.MethodPost + "/api/alertmanager/grafana/config/api/v1/alerts":
// additional authorization is done in the request handler
eval = ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsUpdate), ac.EvalPermission(ac.ActionAlertingNotificationsCreate), ac.EvalPermission(ac.ActionAlertingNotificationsDelete))
eval = ac.EvalAny(ac.EvalPermission(ac.ActionAlertingNotificationsWrite))
case http.MethodPost + "/api/alertmanager/grafana/config/api/v1/receivers/test":
fallback = middleware.ReqEditorRole
eval = ac.EvalPermission(ac.ActionAlertingNotificationsRead)
// External Alertmanager Paths
case http.MethodDelete + "/api/alertmanager/{DatasourceUID}/config/api/v1/alerts":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":DatasourceUID")))
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceUID}/api/v2/status":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScopeUID(ac.Parameter(":DatasourceUID")))
case http.MethodGet + "/api/alertmanager/{DatasourceUID}/config/api/v1/alerts":

View File

@@ -0,0 +1,87 @@
package accesscontrol
import (
"fmt"
"time"
"xorm.io/xorm"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
)
func AddAlertingPermissionsMigrator(mg *migrator.Migrator) {
mg.AddMigration("alerting notification permissions", &alertingMigrator{})
}
type alertingMigrator struct {
sess *xorm.Session
migrator *migrator.Migrator
migrator.MigrationBase
}
var _ migrator.CodeMigration = new(alertingMigrator)
func (m *alertingMigrator) SQL(migrator.Dialect) string {
return "code migration"
}
func (m *alertingMigrator) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
m.sess = sess
m.migrator = migrator
return m.migrateNotificationActions()
}
func (m *alertingMigrator) migrateNotificationActions() error {
var results []accesscontrol.Permission
err := m.sess.Table(&accesscontrol.Permission{}).In("action", "alert.notifications:update", "alert.notifications:create", "alert.notifications:delete", accesscontrol.ActionAlertingNotificationsWrite).Find(&results)
if err != nil {
return fmt.Errorf("failed to query permission table: %w", err)
}
groupByRoleID := make(map[int64]bool)
toDelete := make([]interface{}, 0, len(results))
for _, result := range results {
if result.Action == accesscontrol.ActionAlertingNotificationsWrite {
groupByRoleID[result.RoleID] = false
continue // do not delete this permission
}
if _, ok := groupByRoleID[result.RoleID]; !ok {
groupByRoleID[result.RoleID] = true
}
toDelete = append(toDelete, result.ID)
}
toAdd := make([]accesscontrol.Permission, 0, len(groupByRoleID))
now := time.Now()
for roleID, add := range groupByRoleID {
if !add {
m.migrator.Logger.Info(fmt.Sprintf("skip adding action %s to role ID %d because it is already there", accesscontrol.ActionAlertingNotificationsWrite, roleID))
continue
}
toAdd = append(toAdd, accesscontrol.Permission{
RoleID: roleID,
Action: accesscontrol.ActionAlertingNotificationsWrite,
Scope: "",
Created: now,
Updated: now,
})
}
if len(toAdd) > 0 {
added, err := m.sess.Table(&accesscontrol.Permission{}).InsertMulti(toAdd)
if err != nil {
return fmt.Errorf("failed to insert new permissions:%w", err)
}
m.migrator.Logger.Debug(fmt.Sprintf("updated %d of %d roles with new permission %s", added, len(toAdd), accesscontrol.ActionAlertingNotificationsWrite))
}
if len(toDelete) > 0 {
_, err = m.sess.Table(&accesscontrol.Permission{}).In("id", toDelete...).Delete(accesscontrol.Permission{})
if err != nil {
return fmt.Errorf("failed to delete deprecated permissions [alert.notifications:update, alert.notifications:create, alert.notifications:delete]:%w", err)
}
}
return nil
}

View File

@@ -80,6 +80,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
if mg.Cfg.RBACEnabled {
accesscontrol.AddTeamMembershipMigrations(mg)
accesscontrol.AddDashboardPermissionsMigrator(mg)
accesscontrol.AddAlertingPermissionsMigrator(mg)
}
}
addQueryHistoryStarMigrations(mg)