Files
grafana/pkg/services/sqlstore/migrations/accesscontrol/alerting.go
T
Yuri Tseretyan f2c30cbbd1 Alerting: Protected fields for Contact points (#115442)
* Alerting: Protect sensitive fields of contact points from
 unauthorized modification

- Introduce a new permission alert.notifications.receivers.protected:write. The permission is granted to contact point administrators.
- Introduce field Protected to NotifierOption
- Introduce DiffReport for models.Integrations with focus on Settings. The diff report is extended with methods that return all keys that are different between two settings.
- Add new annotation 'grafana.com/access/CanModifyProtected' to Receiver model
- Update receiver service to enforce the permission and return status 403 if unauthorized user modifies protected field
- Update receiver testing API to enforce permission and return status 403 if unauthorized user modifies protected field.
- Update UI to disable protected fields if user cannot modify them
2025-12-16 15:56:02 -05:00

165 lines
5.4 KiB
Go

package accesscontrol
import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/util/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
}
type receiverCreateScopeMigration struct {
migrator.MigrationBase
}
var _ migrator.CodeMigration = new(alertingMigrator)
func (m *receiverCreateScopeMigration) SQL(migrator.Dialect) string {
return "code migration"
}
func (m *receiverCreateScopeMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
result, err := sess.Exec(`UPDATE permission
SET scope = '',
kind = '',
attribute='',
identifier=''
WHERE action = 'alert.notifications.receivers:create'
AND (scope <> '' OR kind <> '' OR attribute <> '' OR identifier <> '');`)
if result != nil {
aff, _ := result.RowsAffected()
if aff > 0 {
mg.Logger.Info("Removed scope from permission 'alert.notifications.receivers:create'", "affectedRows", aff)
}
}
return err
}
func AddReceiverCreateScopeMigration(mg *migrator.Migrator) {
mg.AddMigration("remove scope from alert.notifications.receivers:create", &receiverCreateScopeMigration{})
}
type receiverProtectedFieldsEditor struct {
migrator.MigrationBase
}
var _ migrator.CodeMigration = new(alertingMigrator)
func (m *receiverProtectedFieldsEditor) SQL(migrator.Dialect) string {
return "code migration"
}
func (m *receiverProtectedFieldsEditor) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
sql := `SELECT *
FROM permission AS P
WHERE action = 'alert.notifications.receivers.secrets:read'
AND EXISTS(SELECT 1 FROM role AS R WHERE R.id = P.role_id AND R.name LIKE 'managed:%')
AND NOT EXISTS(SELECT 1
FROM permission AS P2
WHERE P2.role_id = P.role_id
AND P2.action = 'alert.notifications.receivers.protected:write' AND P2.scope = P.scope
)`
var results []accesscontrol.Permission
if err := sess.SQL(sql).Find(&results); err != nil {
return fmt.Errorf("failed to query permissions: %w", err)
}
permissionsToCreate := make([]accesscontrol.Permission, 0, len(results))
rolesAffected := make(map[int64][]string, 0)
for _, result := range results {
result.ID = 0
result.Action = "alert.notifications.receivers.protected:write"
result.Created = time.Now()
result.Updated = time.Now()
permissionsToCreate = append(permissionsToCreate, result)
rolesAffected[result.RoleID] = append(rolesAffected[result.RoleID], result.Identifier)
}
_, err := sess.InsertMulti(&permissionsToCreate)
for id, ids := range rolesAffected {
mg.Logger.Debug("Added permission 'alert.notifications.receivers.protected:write' to managed role", "roleID", id, "identifiers", ids)
}
return err
}
func AddReceiverProtectedFieldsEditor(mg *migrator.Migrator) {
mg.AddMigration("add 'alert.notifications.receivers.protected:write' to receiver admins", &receiverProtectedFieldsEditor{})
}