diff --git a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
index e2222cffb6a..af5b689ca03 100644
--- a/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
+++ b/docs/sources/enterprise/access-control/fine-grained-access-control-references.md
@@ -66,16 +66,16 @@ Access to Grafana alert rules is an intersection of many permissions:
For more information about the permissions required to access alert rules, refer to [Create a custom role to access alerts in a folder]({{< relref "./usage-scenarios.md#create-a-custom-role-to-access-alerts-in-a-folder" >}}).
-| Fixed roles | Permissions | Descriptions |
-| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*`
`alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules |
-| `fixed:alerting.rules:editor` | All permissions from `fixed:alerting.rules:reader` and
`alert.rule:create`
`alert.rule:update`
`alert.rule:delete` for scope `folders:*`
`alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules. |
-| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope
`alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences. |
-| `fixed:alerting.instances:editor` | All permissions from `fixed:alerting.instances:reader` and
`alert.instances:create`
`alert.instances:update` for organization scope
`alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki. |
-| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies. |
-| `fixed:alerting.notifications:editor` | All permissions from `fixed:alerting.notifications:reader` and
`alert.notifications:create`
`alert.notifications:update`
`alert.notifications:delete` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager. |
-| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader`
`fixed:alerting.instances:reader`
`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies. |
-| `fixed:alerting:editor` | All permissions from `fixed:alerting.rules:editor`
`fixed:alerting.instances:editor`
`fixed:alerting.notifications:editor` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies. |
+| Fixed roles | Permissions | Descriptions |
+| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `fixed:alerting.rules:reader` | `alert.rule:read` for scope `folders:*`
`alert.rules.external:read` for scope `datasources:*` | Read all\* Grafana, Mimir, and Loki alert rules |
+| `fixed:alerting.rules:editor` | All permissions from `fixed:alerting.rules:reader` and
`alert.rule:create`
`alert.rule:update`
`alert.rule:delete` for scope `folders:*`
`alert.rules.external:write` for scope `datasources:*` | Create, update, and delete all\* Grafana, Mimir, and Loki alert rules. |
+| `fixed:alerting.instances:reader` | `alert.instances:read` for organization scope
`alert.instances.external:read` for scope `datasources:*` | Read all alerts and silences in the organization produced by Grafana Alerts and Mimir and Loki alerts and silences. |
+| `fixed:alerting.instances:editor` | All permissions from `fixed:alerting.instances:reader` and
`alert.instances:create`
`alert.instances:update` for organization scope
`alert.instances.external:write` for scope `datasources:*` | Create, update and expire all silences in the organization produced by Grafana, Mimir, and Loki. |
+| `fixed:alerting.notifications:reader` | `alert.notifications:read` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Read all Grafana and Alertmanager contact points, templates, and notification policies. |
+| `fixed:alerting.notifications:editor` | All permissions from `fixed:alerting.notifications:reader` and
`alert.notifications:write` for organization scope
`alert.notifications.external:read` for scope `datasources:*` | Create, update, and delete contact points, templates, mute timings and notification policies for Grafana and external Alertmanager. |
+| `fixed:alerting:reader` | All permissions from `fixed:alerting.rules:reader`
`fixed:alerting.instances:reader`
`fixed:alerting.notifications:reader` | Read-only permissions for all Grafana, Mimir, Loki and Alertmanager alert rules\*, alerts, contact points, and notification policies. |
+| `fixed:alerting:editor` | All permissions from `fixed:alerting.rules:editor`
`fixed:alerting.instances:editor`
`fixed:alerting.notifications:editor` | Create, update, and delete Grafana, Mimir, Loki and Alertmanager alert rules\*, silences, contact points, templates, mute timings, and notification policies. |
## Default built-in role assignments
diff --git a/docs/sources/enterprise/access-control/permissions.md b/docs/sources/enterprise/access-control/permissions.md
index 9f3bf2e009e..ea7066f444d 100644
--- a/docs/sources/enterprise/access-control/permissions.md
+++ b/docs/sources/enterprise/access-control/permissions.md
@@ -128,10 +128,8 @@ The following list contains fine-grained access control actions.
| `alert.instances:update` | n/a | Update and expire silences in the current organization. |
| `alert.instances.external:read` | `datasources:*`
`datasources:uid:*` | Read alerts and silences in data sources that support alerting. |
| `alert.instances.external:write` | `datasources:*`
`datasources:uid:*` | Manage alerts and silences in data sources that support alerting. |
-| `alert.notifications:create` | n/a | Create templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications:read` | n/a | Read all templates, contact points, notification policies, and mute timings in the current organization. |
-| `alert.notifications:update` | n/a | Update templates, contact points, notification policies, and mute timings in the current organization. |
-| `alert.notifications:delete` | n/a | Delete templates, contact points, notification policies, and mute timings in the current organization. |
+| `alert.notifications:write` | n/a | Manage templates, contact points, notification policies, and mute timings in the current organization. |
| `alert.notifications.external:read` | `datasources:*`
`datasources:uid:*` | Read templates, contact points, notification policies, and mute timings in data sources that support alerting. |
| `alert.notifications.external:write` | `datasources:*`
`datasources:uid:*` | Manage templates, contact points, notification policies, and mute timings in data sources that support alerting. |
diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go
index 6c5c3d576c3..5f0a5395e24 100644
--- a/pkg/services/accesscontrol/models.go
+++ b/pkg/services/accesscontrol/models.go
@@ -354,10 +354,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"
diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md
index c7d1f5cda2e..d76e90d81e4 100644
--- a/pkg/services/ngalert/CHANGELOG.md
+++ b/pkg/services/ngalert/CHANGELOG.md
@@ -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
diff --git a/pkg/services/ngalert/accesscontrol.go b/pkg/services/ngalert/accesscontrol.go
index 6c77aff47a8..97340750f13 100644
--- a/pkg/services/ngalert/accesscontrol.go
+++ b/pkg/services/ngalert/accesscontrol.go
@@ -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)},
diff --git a/pkg/services/ngalert/api/authorization.go b/pkg/services/ngalert/api/authorization.go
index 57427637406..e6231309bd0 100644
--- a/pkg/services/ngalert/api/authorization.go
+++ b/pkg/services/ngalert/api/authorization.go
@@ -148,7 +148,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)
@@ -156,14 +156,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/{Recipient}/config/api/v1/alerts":
- eval = ac.EvalPermission(ac.ActionAlertingNotificationsDelete, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient")))
+ eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalWrite, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient")))
case http.MethodGet + "/api/alertmanager/{Recipient}/api/v2/status":
eval = ac.EvalPermission(ac.ActionAlertingNotificationsExternalRead, datasources.ScopeProvider.GetResourceScope(ac.Parameter(":Recipient")))
case http.MethodGet + "/api/alertmanager/{Recipient}/config/api/v1/alerts":
diff --git a/pkg/services/sqlstore/migrations/accesscontrol/alerting.go b/pkg/services/sqlstore/migrations/accesscontrol/alerting.go
new file mode 100644
index 00000000000..ad7a920d7b4
--- /dev/null
+++ b/pkg/services/sqlstore/migrations/accesscontrol/alerting.go
@@ -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
+}
diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go
index c3e22c8a208..0912a5add95 100644
--- a/pkg/services/sqlstore/migrations/migrations.go
+++ b/pkg/services/sqlstore/migrations/migrations.go
@@ -80,6 +80,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
if mg.Cfg.IsFeatureToggleEnabled(featuremgmt.FlagAccesscontrol) {
accesscontrol.AddTeamMembershipMigrations(mg)
accesscontrol.AddDashboardPermissionsMigrator(mg)
+ accesscontrol.AddAlertingPermissionsMigrator(mg)
}
}
addQueryHistoryStarMigrations(mg)
diff --git a/public/app/features/alerting/routes.tsx b/public/app/features/alerting/routes.tsx
index cad6d4cecbe..fbb75c0100d 100644
--- a/public/app/features/alerting/routes.tsx
+++ b/public/app/features/alerting/routes.tsx
@@ -113,7 +113,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/routes/mute-timing/new',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsCreate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -123,7 +123,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/routes/mute-timing/edit',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsUpdate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -173,7 +173,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/notifications/templates/new',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsCreate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -183,7 +183,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/notifications/templates/:id/edit',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsUpdate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -193,7 +193,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/notifications/receivers/new',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsCreate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -203,7 +203,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/notifications/receivers/:id/edit',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsUpdate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
@@ -213,7 +213,7 @@ const unifiedRoutes: RouteDescriptor[] = [
{
path: '/alerting/notifications/global-config',
roles: evaluateAccess(
- [AccessControlAction.AlertingNotificationsUpdate, AccessControlAction.AlertingNotificationsExternalWrite],
+ [AccessControlAction.AlertingNotificationsWrite, AccessControlAction.AlertingNotificationsExternalWrite],
['Editor', 'Admin']
),
component: SafeDynamicImport(
diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx
index e942b1a8c7b..8610b4b0cdf 100644
--- a/public/app/features/alerting/unified/Receivers.test.tsx
+++ b/public/app/features/alerting/unified/Receivers.test.tsx
@@ -137,9 +137,7 @@ describe('Receivers', () => {
mocks.contextSrv.hasPermission.mockImplementation((action) => {
const permissions = [
AccessControlAction.AlertingNotificationsRead,
- AccessControlAction.AlertingNotificationsCreate,
- AccessControlAction.AlertingNotificationsUpdate,
- AccessControlAction.AlertingNotificationsDelete,
+ AccessControlAction.AlertingNotificationsWrite,
AccessControlAction.AlertingNotificationsExternalRead,
AccessControlAction.AlertingNotificationsExternalWrite,
];
diff --git a/public/app/features/alerting/unified/utils/access-control.ts b/public/app/features/alerting/unified/utils/access-control.ts
index 57d785df5ec..6561b005dcc 100644
--- a/public/app/features/alerting/unified/utils/access-control.ts
+++ b/public/app/features/alerting/unified/utils/access-control.ts
@@ -34,15 +34,15 @@ export const notificationsPermissions = {
external: AccessControlAction.AlertingNotificationsExternalRead,
},
create: {
- grafana: AccessControlAction.AlertingNotificationsCreate,
+ grafana: AccessControlAction.AlertingNotificationsWrite,
external: AccessControlAction.AlertingNotificationsExternalWrite,
},
update: {
- grafana: AccessControlAction.AlertingNotificationsUpdate,
+ grafana: AccessControlAction.AlertingNotificationsWrite,
external: AccessControlAction.AlertingNotificationsExternalWrite,
},
delete: {
- grafana: AccessControlAction.AlertingNotificationsDelete,
+ grafana: AccessControlAction.AlertingNotificationsWrite,
external: AccessControlAction.AlertingNotificationsExternalWrite,
},
};
diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts
index 12d2341559f..22bcf3d14c0 100644
--- a/public/app/types/accessControl.ts
+++ b/public/app/types/accessControl.ts
@@ -92,10 +92,8 @@ export enum AccessControlAction {
AlertingInstanceRead = 'alert.instances:read',
// Alerting Notification policies
- AlertingNotificationsCreate = 'alert.notifications:create',
AlertingNotificationsRead = 'alert.notifications:read',
- AlertingNotificationsUpdate = 'alert.notifications:update',
- AlertingNotificationsDelete = 'alert.notifications:delete',
+ AlertingNotificationsWrite = 'alert.notifications:write',
// External alerting rule actions.
AlertingRuleExternalWrite = 'alert.rules.external:write',