From 035bf2914601e79ad147002c1e898ecdf54c590e Mon Sep 17 00:00:00 2001 From: Ieva Date: Wed, 19 Apr 2023 16:34:19 +0100 Subject: [PATCH] RBAC: Remove the option to disable RBAC and add automated permission migrations for instances that had RBAC disabled (#66652) * RBAC: Stop reading enabeld from ini file and always set to true * Migrations: Add a migration for rbac to reset data migrations if rbac was disabled * If rbac was disabled we reset the data and data migrations that rbac has to perform to get it to a correct state * Migrator: Store migration logs on migrator and add function to clear it from the in-memory stored logs * update tests --------- Co-authored-by: Karl Persson --- .../accesscontrol/disabled_migration.go | 87 +++++++++++++++++++ .../migrations/accesscontrol/test/ac_test.go | 8 +- .../sqlstore/migrations/migrations.go | 1 + .../sqlstore/migrations/migrations_test.go | 3 +- .../migrations/ualert/migration_test.go | 3 +- pkg/services/sqlstore/migrator/migrator.go | 12 ++- pkg/setting/setting.go | 2 +- 7 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 pkg/services/sqlstore/migrations/accesscontrol/disabled_migration.go diff --git a/pkg/services/sqlstore/migrations/accesscontrol/disabled_migration.go b/pkg/services/sqlstore/migrations/accesscontrol/disabled_migration.go new file mode 100644 index 00000000000..482966fa0f4 --- /dev/null +++ b/pkg/services/sqlstore/migrations/accesscontrol/disabled_migration.go @@ -0,0 +1,87 @@ +package accesscontrol + +import ( + "fmt" + + "xorm.io/xorm" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +const ( + disabledMigrationID = "rbac disabled migrator" + teamMigrationID = "teams permissions migration" + dashboardMigrationID = "dashboard permissions" + dashboardsUIDMigrationID = "dashboard permissions uid scopes" + datasourceMigrationID = "data source permissions" + datasourceUIDMigrationID = "data source uid permissions" + managedPermissionsMigrationID = "managed permissions migration" + alertFolderMigrationID = "managed folder permissions alert actions repeated migration" + managedPermissionsEnterpriseMigrationID = "managed permissions migration enterprise" +) + +var migrations = [...]string{ + teamMigrationID, + dashboardMigrationID, + dashboardsUIDMigrationID, + datasourceMigrationID, + datasourceUIDMigrationID, + managedPermissionsMigrationID, + alertFolderMigrationID, + managedPermissionsEnterpriseMigrationID, +} + +func AddDisabledMigrator(mg *migrator.Migrator) { + mg.AddMigration(disabledMigrationID, &DisabledMigrator{}) +} + +type DisabledMigrator struct { + migrator.MigrationBase +} + +func (m *DisabledMigrator) SQL(dialect migrator.Dialect) string { + return CodeMigrationSQL +} + +func (m *DisabledMigrator) Exec(sess *xorm.Session, mg *migrator.Migrator) error { + enabled := mg.Cfg.Raw.Section("rbac").Key("enabled").MustBool(true) + if enabled { + // if the flag is enabled we skip the reset of data migrations + mg.Logger.Debug("skip reset of rbac data migrations") + return nil + } + + if _, err := sess.Exec("DELETE FROM builtin_role WHERE role_id IN (SELECT id FROM role WHERE name LIKE 'managed:%')"); err != nil { + return fmt.Errorf("failed to remove basic role bindings: %w", err) + } + + if _, err := sess.Exec("DELETE FROM team_role WHERE role_id IN (SELECT id FROM role WHERE name LIKE 'managed:%')"); err != nil { + return fmt.Errorf("failed to remove team role bindings: %w", err) + } + + if _, err := sess.Exec("DELETE FROM user_role where role_id IN (SELECT id FROM role WHERE name LIKE 'managed:%')"); err != nil { + return fmt.Errorf("failed to remove user role bindings: %w", err) + } + + if _, err := sess.Exec("DELETE FROM permission WHERE role_id IN (SELECT id FROM role WHERE name LIKE 'managed:%');"); err != nil { + return fmt.Errorf("failed to remove managed rbac permission: %w", err) + } + + if _, err := sess.Exec("DELETE FROM role WHERE name LIKE 'managed:%';"); err != nil { + return fmt.Errorf("failed to remove managed rbac roles: %w", err) + } + + params := []interface{}{"DELETE FROM migration_log WHERE migration_id IN (?, ?, ?, ?, ?, ?, ?, ?)"} + for _, m := range migrations { + params = append(params, m) + } + + if _, err := sess.Exec(params...); err != nil { + return fmt.Errorf("failed to remove managed permissions migrations: %w", err) + } + + // Note: we also need to clear migration from the in-memory representation of migration log + mg.RemoveMigrationLogs(migrations[:]...) + + return nil +} diff --git a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go index 0c5ae95ae3d..0dcae982257 100644 --- a/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go +++ b/pkg/services/sqlstore/migrations/accesscontrol/test/ac_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/ini.v1" "xorm.io/xorm" "github.com/grafana/grafana/pkg/infra/log" @@ -153,6 +154,7 @@ func TestMigrations(t *testing.T) { config: &setting.Cfg{ EditorsCanAdmin: true, IsFeatureToggleEnabled: func(key string) bool { return key == "accesscontrol" }, + Raw: ini.Empty(), }, expectedRolePerms: map[string][]rawPermission{ "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, @@ -181,6 +183,7 @@ func TestMigrations(t *testing.T) { desc: "without editors can admin", config: &setting.Cfg{ IsFeatureToggleEnabled: func(key string) bool { return key == "accesscontrol" }, + Raw: ini.Empty(), }, expectedRolePerms: map[string][]rawPermission{ "managed:users:1:permissions": {{Action: "teams:read", Scope: team1Scope}}, @@ -256,7 +259,10 @@ func setupTestDB(t *testing.T) *xorm.Engine { err = migrator.NewDialect(x).CleanDB() require.NoError(t, err) - mg := migrator.NewMigrator(x, &setting.Cfg{Logger: log.New("acmigration.test")}) + mg := migrator.NewMigrator(x, &setting.Cfg{ + Logger: log.New("acmigration.test"), + Raw: ini.Empty(), + }) migrations := &migrations.OSSMigrations{} migrations.AddMigration(mg) diff --git a/pkg/services/sqlstore/migrations/migrations.go b/pkg/services/sqlstore/migrations/migrations.go index 659cfff71e1..b24b2531659 100644 --- a/pkg/services/sqlstore/migrations/migrations.go +++ b/pkg/services/sqlstore/migrations/migrations.go @@ -61,6 +61,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) { accesscontrol.AddMigration(mg) addQueryHistoryMigrations(mg) + accesscontrol.AddDisabledMigrator(mg) accesscontrol.AddTeamMembershipMigrations(mg) accesscontrol.AddDashboardPermissionsMigrator(mg) accesscontrol.AddAlertingPermissionsMigrator(mg) diff --git a/pkg/services/sqlstore/migrations/migrations_test.go b/pkg/services/sqlstore/migrations/migrations_test.go index 96218fb3c93..7645e21cfb8 100644 --- a/pkg/services/sqlstore/migrations/migrations_test.go +++ b/pkg/services/sqlstore/migrations/migrations_test.go @@ -12,6 +12,7 @@ import ( "github.com/go-sql-driver/mysql" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "gopkg.in/ini.v1" "xorm.io/xorm" . "github.com/grafana/grafana/pkg/services/sqlstore/migrator" @@ -33,7 +34,7 @@ func TestMigrations(t *testing.T) { _, err = x.SQL(query).Get(&result) require.Error(t, err) - mg := NewMigrator(x, &setting.Cfg{}) + mg := NewMigrator(x, &setting.Cfg{Raw: ini.Empty()}) migrations := &OSSMigrations{} migrations.AddMigration(mg) expectedMigrations := mg.GetMigrationIDs(true) diff --git a/pkg/services/sqlstore/migrations/ualert/migration_test.go b/pkg/services/sqlstore/migrations/ualert/migration_test.go index 7e4c51d59fe..9f6b62891d7 100644 --- a/pkg/services/sqlstore/migrations/ualert/migration_test.go +++ b/pkg/services/sqlstore/migrations/ualert/migration_test.go @@ -11,6 +11,7 @@ import ( "github.com/prometheus/alertmanager/pkg/labels" "github.com/prometheus/common/model" "github.com/stretchr/testify/require" + "gopkg.in/ini.v1" "xorm.io/xorm" "github.com/grafana/grafana/pkg/components/simplejson" @@ -629,7 +630,7 @@ func setupTestDB(t *testing.T) *xorm.Engine { err = migrator.NewDialect(x).CleanDB() require.NoError(t, err) - mg := migrator.NewMigrator(x, &setting.Cfg{}) + mg := migrator.NewMigrator(x, &setting.Cfg{Raw: ini.Empty()}) migrations := &migrations.OSSMigrations{} migrations.AddMigration(mg) diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index a9ab04d987c..0223e7ba52f 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -27,6 +27,7 @@ type Migrator struct { Logger log.Logger Cfg *setting.Cfg isLocked atomic.Bool + logMap map[string]MigrationLog } type MigrationLog struct { @@ -97,9 +98,16 @@ func (mg *Migrator) GetMigrationLog() (map[string]MigrationLog, error) { logMap[logItem.MigrationID] = logItem } + mg.logMap = logMap return logMap, nil } +func (mg *Migrator) RemoveMigrationLogs(migrationsIDs ...string) { + for _, id := range migrationsIDs { + delete(mg.logMap, id) + } +} + func (mg *Migrator) Start(isDatabaseLockingEnabled bool, lockAttemptTimeout int) (err error) { if !isDatabaseLockingEnabled { return mg.run() @@ -128,7 +136,7 @@ func (mg *Migrator) Start(isDatabaseLockingEnabled bool, lockAttemptTimeout int) func (mg *Migrator) run() (err error) { mg.Logger.Info("Starting DB migrations") - logMap, err := mg.GetMigrationLog() + _, err = mg.GetMigrationLog() if err != nil { return err } @@ -138,7 +146,7 @@ func (mg *Migrator) run() (err error) { start := time.Now() for _, m := range mg.migrations { m := m - _, exists := logMap[m.Id()] + _, exists := mg.logMap[m.Id()] if exists { mg.Logger.Debug("Skipping migration: Already executed", "id", m.Id()) migrationsSkipped++ diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 06732a6d056..0ddd92535ee 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -1564,7 +1564,7 @@ func readAuthSettings(iniFile *ini.File, cfg *Cfg) (err error) { func readAccessControlSettings(iniFile *ini.File, cfg *Cfg) { rbac := iniFile.Section("rbac") - cfg.RBACEnabled = rbac.Key("enabled").MustBool(true) + cfg.RBACEnabled = true cfg.RBACPermissionCache = rbac.Key("permission_cache").MustBool(true) cfg.RBACPermissionValidationEnabled = rbac.Key("permission_validation_enabled").MustBool(false) cfg.RBACResetBasicRoles = rbac.Key("reset_basic_roles").MustBool(false)