grafana/pkg/services/secrets/kvstore/plugin_fatal_crash_test.go
Michael Mandrus 72d9de3a0f
Secrets: Implement Secret Plugin required flag and fatal crash on startup (#52552)
* add special handling on the plugin gathering side to check whether secrets manager plugins are enabled or not

* show disabled badge in front end if the plugin is not enabled

* Only show error in disabled badge hover if one is present (otherwise it shows "undefined")

* refactor to make use of fields already available in the DTO

* fix typo

* if there is no error returned for the plugin, just show 'disabled'

* fix typo

* Update public/app/features/plugins/admin/components/Badges/PluginDisabledBadge.tsx

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>

* Update frontendsettings.go

add clarifying comment

* fix unit test

* rework task to use new frontend property combined with plugin type to determine if the plugin should be disabled

* Update helpers.test.ts

revert test change

* fix unit test

* show custom uninstall message if the plugin is a secrets manager

* bogus commit to trigger precommit

* undo commit

* run precommit manually

* add some consts

* refactor a bit to pull plugin error management up a level

* re-add code squashed in merge

* fix compile issues

* add code to set plugin error fatal flag after secret migration

* refactor to move plugin startup out of Should Check func

* re-add important check

* make plugin startup errors fatal the first time we set a secret on the plugin

* rename func to make intent clearler

* remove unnecessary duplicate code from plugin mig

* fix compile error

* fix more compile errors

* add some extra logging to secrets migration

* have remote_plugin secret service managed plugin error fatal flag directly

* add blank file for eventual unit tests

* fix linting issues

* changes from PR review

* quick bit of cleanup

* add comment explaining design decision

* move more common test helpers to file

* slightly update to first time Get secret call

* add unit tests

* remove override func from provider

* fix linting issues

* add test cleanup step

* add some comments about refactoring to hacky test function

Co-authored-by: Levente Balogh <balogh.levente.hu@gmail.com>
2022-07-25 12:37:47 -04:00

150 lines
5.1 KiB
Go

package kvstore
import (
"context"
"errors"
"sync"
"testing"
"github.com/grafana/grafana/pkg/infra/kvstore"
"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/services/sqlstore/mockstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
)
// Set fatal flag to true, then simulate a plugin start failure
// Should result in an error from the secret store provider
func TestFatalPluginErr_PluginFailsToStartWithFatalFlagSet(t *testing.T) {
svc, _, _, err := setupFatalCrashTest(t, true, true, false)
require.Error(t, err)
require.Nil(t, svc)
}
// Set fatal flag to false, then simulate a plugin start failure
// Should result in the secret store provider returning the sql impl
func TestFatalPluginErr_PluginFailsToStartWithFatalFlagNotSet(t *testing.T) {
svc, _, _, err := setupFatalCrashTest(t, true, false, false)
require.NoError(t, err)
require.IsType(t, &CachedKVStore{}, svc)
cachedKv, _ := svc.(*CachedKVStore)
require.IsType(t, &secretsKVStoreSQL{}, cachedKv.GetUnwrappedStore())
}
// With fatal flag not set, store a secret in the plugin while backwards compatibility is disabled
// Should result in the fatal flag going from unset -> set to true
func TestFatalPluginErr_FatalFlagGetsSetWithBackwardsCompatDisabled(t *testing.T) {
svc, kvstore, _, err := setupFatalCrashTest(t, false, false, true)
require.NoError(t, err)
require.NotNil(t, svc)
err = svc.Set(context.Background(), 0, "datasource", "postgres", "my secret")
require.NoError(t, err)
isFatal, err := isPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore))
require.NoError(t, err)
require.True(t, isFatal)
}
// With fatal flag set, retrieve a secret from the plugin while backwards compatibility is enabled
// Should result in the fatal flag going from set to true -> unset
func TestFatalPluginErr_FatalFlagGetsUnSetWithBackwardsCompatEnabled(t *testing.T) {
svc, kvstore, _, err := setupFatalCrashTest(t, false, true, false)
require.NoError(t, err)
require.NotNil(t, svc)
val, exists, err := svc.Get(context.Background(), 0, "datasource", "postgres")
require.NoError(t, err)
require.NotNil(t, val)
require.True(t, exists)
isFatal, err := isPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore))
require.NoError(t, err)
require.False(t, isFatal)
}
// With fatal flag unset, do a migration with backwards compatibility disabled. When unified secrets are deleted, return an error on the first deletion
// Should result in the fatal flag remaining unset
func TestFatalPluginErr_MigrationTestWithErrorDeletingUnifiedSecrets(t *testing.T) {
svc, kvstore, _, err := setupFatalCrashTest(t, false, false, true)
require.NoError(t, err)
migration := setupTestMigratorServiceWithDeletionError(t, svc, &mockstore.SQLStoreMock{
ExpectedError: errors.New("random error"),
}, kvstore)
err = migration.Migrate(context.Background())
require.Error(t, err)
isFatal, err := isPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore))
require.NoError(t, err)
require.False(t, isFatal)
}
func setupFatalCrashTest(
t *testing.T,
shouldRemoteCheckError bool,
isPluginErrorFatal bool,
isBackwardsCompatDisabled bool,
) (SecretsKVStore, kvstore.KVStore, *sqlstore.SQLStore, error) {
t.Helper()
sqlStore := sqlstore.InitTestDB(t)
secretService := fakes.FakeSecretsService{}
var remoteCheck *mockRemoteSecretsPluginCheck
if shouldRemoteCheckError {
remoteCheck = provideMockRemotePluginCheckWithErr()
} else {
remoteCheck = provideMockRemotePluginCheck()
}
kvstore := kvstore.ProvideService(sqlStore)
if isPluginErrorFatal {
_ = setPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore), true)
}
features := NewFakeFeatureToggles(t, isBackwardsCompatDisabled)
svc, err := ProvideService(sqlStore, secretService, remoteCheck, kvstore, features)
t.Cleanup(func() {
fatalFlagOnce = sync.Once{}
})
return svc, kvstore, sqlStore, err
}
func setupTestMigratorServiceWithDeletionError(
t *testing.T,
secretskv SecretsKVStore,
sqlStore sqlstore.Store,
kvstore kvstore.KVStore,
) *PluginSecretMigrationService {
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}
remoteCheck := provideMockRemotePluginCheck()
secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore())
getAllFuncOverride := func(ctx context.Context) ([]Item, error) {
items := make([]Item, 0)
var orgId int64 = 1
str := "random string"
items = append(items, Item{
Id: 1,
OrgId: &orgId,
Type: &str,
Namespace: &str,
Value: "bogus",
})
return items, nil
}
migratorService := ProvidePluginSecretMigrationService(
secretskv,
cfg,
sqlStore,
secretsService,
remoteCheck,
kvstore,
)
// TODO refactor Migrator to allow us to override the entire sqlstore with a mock instead
migratorService.overrideGetAllFunc(getAllFuncOverride)
return migratorService
}