f2c30cbbd1
* 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
165 lines
5.4 KiB
Go
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{})
|
|
}
|