grafana/pkg/services/secrets/kvstore/kvstore.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

140 lines
4.6 KiB
Go

package kvstore
import (
"context"
"errors"
"fmt"
"time"
"github.com/grafana/grafana/pkg/infra/kvstore"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/sqlstore"
)
const (
// Wildcard to query all organizations
AllOrganizations = -1
)
func ProvideService(
sqlStore sqlstore.Store,
secretsService secrets.Service,
remoteCheck UseRemoteSecretsPluginCheck,
kvstore kvstore.KVStore,
features featuremgmt.FeatureToggles,
) (SecretsKVStore, error) {
var store SecretsKVStore
logger := log.New("secrets.kvstore")
store = &secretsKVStoreSQL{
sqlStore: sqlStore,
secretsService: secretsService,
log: logger,
decryptionCache: decryptionCache{
cache: make(map[int64]cachedDecrypted),
},
}
if remoteCheck.ShouldUseRemoteSecretsPlugin() {
// Attempt to start the plugin
secretsPlugin, err := remoteCheck.StartAndReturnPlugin(context.Background())
namespacedKVStore := GetNamespacedKVStore(kvstore)
if err != nil || secretsPlugin == nil {
if isFatal, err2 := isPluginStartupErrorFatal(context.Background(), namespacedKVStore); isFatal || err2 != nil {
// plugin error was fatal or there was an error determining if the error was fatal
logger.Error("secrets management plugin is required to start -- exiting app")
if err2 != nil {
// TODO decide whether an error here should actually crash the app
return nil, err2
}
return nil, err
}
logger.Error("error starting secrets plugin, falling back to SQL implementation")
} else {
store = &secretsKVStorePlugin{
secretsPlugin: secretsPlugin,
secretsService: secretsService,
log: logger,
kvstore: namespacedKVStore,
backwardsCompatibilityDisabled: features.IsEnabled(featuremgmt.FlagDisableSecretsCompatibility),
}
}
} else {
logger.Debug("secrets kvstore is using the default (SQL) implementation for secrets management")
}
return NewCachedKVStore(store, 5*time.Second, 5*time.Minute), nil
}
// SecretsKVStore is an interface for k/v store.
type SecretsKVStore interface {
Get(ctx context.Context, orgId int64, namespace string, typ string) (string, bool, error)
Set(ctx context.Context, orgId int64, namespace string, typ string, value string) error
Del(ctx context.Context, orgId int64, namespace string, typ string) error
Keys(ctx context.Context, orgId int64, namespace string, typ string) ([]Key, error)
Rename(ctx context.Context, orgId int64, namespace string, typ string, newNamespace string) error
}
// WithType returns a kvstore wrapper with fixed orgId and type.
func With(kv SecretsKVStore, orgId int64, namespace string, typ string) *FixedKVStore {
return &FixedKVStore{
kvStore: kv,
OrgId: orgId,
Namespace: namespace,
Type: typ,
}
}
// FixedKVStore is a SecretsKVStore wrapper with fixed orgId, namespace and type.
type FixedKVStore struct {
kvStore SecretsKVStore
OrgId int64
Namespace string
Type string
}
func (kv *FixedKVStore) Get(ctx context.Context) (string, bool, error) {
return kv.kvStore.Get(ctx, kv.OrgId, kv.Namespace, kv.Type)
}
func (kv *FixedKVStore) Set(ctx context.Context, value string) error {
return kv.kvStore.Set(ctx, kv.OrgId, kv.Namespace, kv.Type, value)
}
func (kv *FixedKVStore) Del(ctx context.Context) error {
return kv.kvStore.Del(ctx, kv.OrgId, kv.Namespace, kv.Type)
}
func (kv *FixedKVStore) Keys(ctx context.Context) ([]Key, error) {
return kv.kvStore.Keys(ctx, kv.OrgId, kv.Namespace, kv.Type)
}
func (kv *FixedKVStore) Rename(ctx context.Context, newNamespace string) error {
err := kv.kvStore.Rename(ctx, kv.OrgId, kv.Namespace, kv.Type, newNamespace)
if err != nil {
return err
}
kv.Namespace = newNamespace
return nil
}
// Helpers to determine whether plugin startup failures are fatal to the app
func GetNamespacedKVStore(kv kvstore.KVStore) *kvstore.NamespacedKVStore {
return kvstore.WithNamespace(kv, kvstore.AllOrganizations, PluginNamespace)
}
func isPluginStartupErrorFatal(ctx context.Context, kvstore *kvstore.NamespacedKVStore) (bool, error) {
_, exists, err := kvstore.Get(ctx, QuitOnPluginStartupFailureKey)
if err != nil {
return false, errors.New(fmt.Sprint("error retrieving key ", QuitOnPluginStartupFailureKey, " from kvstore. error: ", err.Error()))
}
return exists, nil
}
func setPluginStartupErrorFatal(ctx context.Context, kvstore *kvstore.NamespacedKVStore, isFatal bool) error {
if !isFatal {
return kvstore.Del(ctx, QuitOnPluginStartupFailureKey)
}
return kvstore.Set(ctx, QuitOnPluginStartupFailureKey, "true")
}