mirror of https://github.com/grafana/grafana
Secret migration from Sql KV Store to Secret Plugin (#52191)
* Created PluginSecretMigrationService to be able to migrate from the secrets table from the database to the secret plugin. Added migration which takes all the secrets at the sql store and stores it in the plugin. Then deletes all the secrets from the sql * Added secretsKVStoreSQL.GetAll() method to return all the secrets at the sql table * Renaming kvstore_test.go as sql_test.go, adding GetAll test case. Fixing decryption of keyspull/52481/head
parent
b742e80930
commit
e1785f4eb4
@ -0,0 +1,75 @@ |
||||
package kvstore |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/secrets" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
) |
||||
|
||||
// PluginSecretMigrationService This migrator will handle migration of datasource secrets (aka Unified secrets)
|
||||
// into the plugin secrets configured
|
||||
type PluginSecretMigrationService struct { |
||||
secretsStore SecretsKVStore |
||||
cfg *setting.Cfg |
||||
logger log.Logger |
||||
sqlStore sqlstore.Store |
||||
secretsService secrets.Service |
||||
remoteCheck UseRemoteSecretsPluginCheck |
||||
} |
||||
|
||||
func ProvidePluginSecretMigrationService( |
||||
secretsStore SecretsKVStore, |
||||
cfg *setting.Cfg, |
||||
sqlStore sqlstore.Store, |
||||
secretsService secrets.Service, |
||||
remoteCheck UseRemoteSecretsPluginCheck, |
||||
) *PluginSecretMigrationService { |
||||
return &PluginSecretMigrationService{ |
||||
secretsStore: secretsStore, |
||||
cfg: cfg, |
||||
logger: log.New("sec-plugin-mig"), |
||||
sqlStore: sqlStore, |
||||
secretsService: secretsService, |
||||
remoteCheck: remoteCheck, |
||||
} |
||||
} |
||||
|
||||
func (s *PluginSecretMigrationService) Migrate(ctx context.Context) error { |
||||
// Check if we should migrate to plugin - default false
|
||||
if s.cfg.SectionWithEnvOverrides("secrets").Key("migrate_to_plugin").MustBool(false) && |
||||
s.remoteCheck.ShouldUseRemoteSecretsPlugin() { |
||||
// we need to instantiate the secretsKVStore as this is not on wire, and in this scenario,
|
||||
// the secrets store would be the plugin.
|
||||
secretsSql := &secretsKVStoreSQL{ |
||||
sqlStore: s.sqlStore, |
||||
secretsService: s.secretsService, |
||||
log: s.logger, |
||||
decryptionCache: decryptionCache{ |
||||
cache: make(map[int64]cachedDecrypted), |
||||
}, |
||||
} |
||||
|
||||
allSec, err := secretsSql.GetAll(ctx) |
||||
if err != nil { |
||||
return nil |
||||
} |
||||
// We just set it again as the current secret store should be the plugin secret
|
||||
for _, sec := range allSec { |
||||
err = s.secretsStore.Set(ctx, *sec.OrgId, *sec.Namespace, *sec.Type, sec.Value) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
// as no err was returned, when we delete all the secrets from the sql store
|
||||
for _, sec := range allSec { |
||||
err = secretsSql.Del(ctx, *sec.OrgId, *sec.Namespace, *sec.Type) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,121 @@ |
||||
package kvstore |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin" |
||||
"github.com/grafana/grafana/pkg/services/secrets/fakes" |
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager" |
||||
"github.com/grafana/grafana/pkg/services/sqlstore" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
"github.com/stretchr/testify/require" |
||||
"gopkg.in/ini.v1" |
||||
) |
||||
|
||||
// This tests will create a mock sql database and an inmemory
|
||||
// implementation of the secret manager to simulate the plugin.
|
||||
func TestPluginSecretMigrationService_Migrate(t *testing.T) { |
||||
ctx := context.Background() |
||||
|
||||
t.Run("migration run ok - 2 secrets migrated", func(t *testing.T) { |
||||
// --- SETUP
|
||||
migratorService, secretsStore, sqlSecretStore := setupTestMigratorService(t) |
||||
var orgId int64 = 1 |
||||
namespace1, namespace2 := "namespace-test", "namespace-test2" |
||||
typ := "type-test" |
||||
value := "SUPER_SECRET" |
||||
|
||||
addSecretToSqlStore(t, sqlSecretStore, ctx, orgId, namespace1, typ, value) |
||||
addSecretToSqlStore(t, sqlSecretStore, ctx, orgId, namespace2, typ, value) |
||||
|
||||
// --- EXECUTION
|
||||
err := migratorService.Migrate(ctx) |
||||
require.NoError(t, err) |
||||
|
||||
// --- VALIDATIONS
|
||||
validateSecretWasDeleted(t, sqlSecretStore, ctx, orgId, namespace1, typ) |
||||
validateSecretWasDeleted(t, sqlSecretStore, ctx, orgId, namespace2, typ) |
||||
|
||||
validateSecretWasStoreInPlugin(t, secretsStore, ctx, orgId, namespace1, typ) |
||||
validateSecretWasStoreInPlugin(t, secretsStore, ctx, orgId, namespace1, typ) |
||||
}) |
||||
} |
||||
|
||||
func addSecretToSqlStore(t *testing.T, sqlSecretStore *secretsKVStoreSQL, ctx context.Context, orgId int64, namespace1 string, typ string, value string) { |
||||
err := sqlSecretStore.Set(ctx, orgId, namespace1, typ, value) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
// validates that secrets on the sql store were deleted.
|
||||
func validateSecretWasDeleted(t *testing.T, sqlSecretStore *secretsKVStoreSQL, ctx context.Context, orgId int64, namespace1 string, typ string) { |
||||
res, err := sqlSecretStore.Keys(ctx, orgId, namespace1, typ) |
||||
require.NoError(t, err) |
||||
require.Equal(t, 0, len(res)) |
||||
} |
||||
|
||||
// validates that secrets should be on the plugin
|
||||
func validateSecretWasStoreInPlugin(t *testing.T, secretsStore SecretsKVStore, ctx context.Context, orgId int64, namespace1 string, typ string) { |
||||
resPlugin, err := secretsStore.Keys(ctx, orgId, namespace1, typ) |
||||
require.NoError(t, err) |
||||
require.Equal(t, 1, len(resPlugin)) |
||||
} |
||||
|
||||
//
|
||||
func setupTestMigratorService(t *testing.T) (*PluginSecretMigrationService, SecretsKVStore, *secretsKVStoreSQL) { |
||||
t.Helper() |
||||
|
||||
rawCfg := ` |
||||
[secrets] |
||||
use_plugin = true |
||||
migrate_to_plugin = true |
||||
` |
||||
raw, err := ini.Load([]byte(rawCfg)) |
||||
require.NoError(t, err) |
||||
cfg := &setting.Cfg{Raw: raw} |
||||
// this would be the plugin - mocked at the moment
|
||||
secretsStoreForPlugin := NewFakeSecretsKVStore() |
||||
// Mocked remote plugin check, always return true
|
||||
remoteCheck := provideMockRemotePluginCheck() |
||||
|
||||
// this is to init the sql secret store inside the migration
|
||||
sqlStore := sqlstore.InitTestDB(t) |
||||
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) |
||||
|
||||
migratorService := ProvidePluginSecretMigrationService( |
||||
secretsStoreForPlugin, |
||||
cfg, |
||||
sqlStore, |
||||
secretsService, |
||||
remoteCheck, |
||||
) |
||||
|
||||
secretsSql := &secretsKVStoreSQL{ |
||||
sqlStore: sqlStore, |
||||
secretsService: secretsService, |
||||
log: log.New("test.logger"), |
||||
decryptionCache: decryptionCache{ |
||||
cache: make(map[int64]cachedDecrypted), |
||||
}, |
||||
} |
||||
|
||||
return migratorService, secretsStoreForPlugin, secretsSql |
||||
} |
||||
|
||||
//
|
||||
type mockRemoteSecretsPluginCheck struct { |
||||
UseRemoteSecretsPluginCheck |
||||
} |
||||
|
||||
func provideMockRemotePluginCheck() *mockRemoteSecretsPluginCheck { |
||||
return &mockRemoteSecretsPluginCheck{} |
||||
} |
||||
|
||||
func (c *mockRemoteSecretsPluginCheck) ShouldUseRemoteSecretsPlugin() bool { |
||||
return true |
||||
} |
||||
|
||||
func (c *mockRemoteSecretsPluginCheck) GetPlugin() (secretsmanagerplugin.SecretsManagerPlugin, error) { |
||||
return nil, nil |
||||
} |
Loading…
Reference in new issue