mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
330 lines
10 KiB
Go
330 lines
10 KiB
Go
package kvstore
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc"
|
|
"gopkg.in/ini.v1"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
|
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
|
pluginsLogger "github.com/grafana/grafana/pkg/plugins/log"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/secrets/fakes"
|
|
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
func NewFakeSQLSecretsKVStore(t *testing.T) *SecretsKVStoreSQL {
|
|
t.Helper()
|
|
sqlStore := db.InitTestDB(t)
|
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
|
return NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
|
}
|
|
|
|
func NewFakePluginSecretsKVStore(t *testing.T, features featuremgmt.FeatureToggles, fallback SecretsKVStore) *SecretsKVStorePlugin {
|
|
t.Helper()
|
|
sqlStore := db.InitTestDB(t)
|
|
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
|
store := kvstore.ProvideService(sqlStore)
|
|
namespacedKVStore := GetNamespacedKVStore(store)
|
|
manager := NewFakeSecretsPluginManager(t, false)
|
|
plugin := manager.SecretsManager(context.Background()).SecretsManager
|
|
return NewPluginSecretsKVStore(plugin, secretsService, namespacedKVStore, features, fallback, log.New("test.logger"))
|
|
}
|
|
|
|
// In memory kv store used for testing
|
|
type FakeSecretsKVStore struct {
|
|
store map[Key]string
|
|
delError bool
|
|
fallback SecretsKVStore
|
|
}
|
|
|
|
func NewFakeSecretsKVStore() *FakeSecretsKVStore {
|
|
return &FakeSecretsKVStore{store: make(map[Key]string)}
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) DeletionError(shouldErr bool) {
|
|
f.delError = shouldErr
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) Get(ctx context.Context, orgId int64, namespace string, typ string) (string, bool, error) {
|
|
value := f.store[buildKey(orgId, namespace, typ)]
|
|
found := value != ""
|
|
return value, found, nil
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) Set(ctx context.Context, orgId int64, namespace string, typ string, value string) error {
|
|
f.store[buildKey(orgId, namespace, typ)] = value
|
|
return nil
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) Del(ctx context.Context, orgId int64, namespace string, typ string) error {
|
|
if f.delError {
|
|
return errors.New("mocked del error")
|
|
}
|
|
delete(f.store, buildKey(orgId, namespace, typ))
|
|
return nil
|
|
}
|
|
|
|
// List all keys with an optional filter. If default values are provided, filter is not applied.
|
|
func (f *FakeSecretsKVStore) Keys(ctx context.Context, orgId int64, namespace string, typ string) ([]Key, error) {
|
|
res := make([]Key, 0)
|
|
for k := range f.store {
|
|
if orgId == AllOrganizations && namespace == "" && typ == "" {
|
|
res = append(res, k)
|
|
} else if k.OrgId == orgId && k.Namespace == namespace && k.Type == typ {
|
|
res = append(res, k)
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) Rename(ctx context.Context, orgId int64, namespace string, typ string, newNamespace string) error {
|
|
f.store[buildKey(orgId, newNamespace, typ)] = f.store[buildKey(orgId, namespace, typ)]
|
|
delete(f.store, buildKey(orgId, namespace, typ))
|
|
return nil
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) GetAll(ctx context.Context) ([]Item, error) {
|
|
items := make([]Item, 0)
|
|
for k := range f.store {
|
|
orgId := k.OrgId
|
|
namespace := k.Namespace
|
|
typ := k.Type
|
|
items = append(items, Item{
|
|
OrgId: &orgId,
|
|
Namespace: &namespace,
|
|
Type: &typ,
|
|
Value: f.store[k],
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) Fallback() SecretsKVStore {
|
|
return f.fallback
|
|
}
|
|
|
|
func (f *FakeSecretsKVStore) SetFallback(store SecretsKVStore) error {
|
|
f.fallback = store
|
|
return nil
|
|
}
|
|
|
|
func buildKey(orgId int64, namespace string, typ string) Key {
|
|
return Key{
|
|
OrgId: orgId,
|
|
Namespace: namespace,
|
|
Type: typ,
|
|
}
|
|
}
|
|
|
|
func internalToProtoKey(k Key) *secretsmanagerplugin.Key {
|
|
return &secretsmanagerplugin.Key{
|
|
OrgId: k.OrgId,
|
|
Namespace: k.Namespace,
|
|
Type: k.Type,
|
|
}
|
|
}
|
|
|
|
// Fake feature toggle - only need to check the backwards compatibility disabled flag
|
|
type fakeFeatureToggles struct {
|
|
returnValue bool
|
|
}
|
|
|
|
func NewFakeFeatureToggles(t *testing.T, returnValue bool) featuremgmt.FeatureToggles {
|
|
t.Helper()
|
|
return fakeFeatureToggles{
|
|
returnValue: returnValue,
|
|
}
|
|
}
|
|
|
|
func (f fakeFeatureToggles) IsEnabledGlobally(feature string) bool {
|
|
return f.returnValue
|
|
}
|
|
|
|
func (f fakeFeatureToggles) IsEnabled(ctx context.Context, feature string) bool {
|
|
return f.returnValue
|
|
}
|
|
|
|
func (f fakeFeatureToggles) GetEnabled(ctx context.Context) map[string]bool {
|
|
return map[string]bool{}
|
|
}
|
|
|
|
// Fake grpc secrets plugin impl
|
|
type fakeGRPCSecretsPlugin struct {
|
|
kv map[Key]string
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) GetSecret(ctx context.Context, in *secretsmanagerplugin.GetSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.GetSecretResponse, error) {
|
|
val, ok := c.kv[buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)]
|
|
return &secretsmanagerplugin.GetSecretResponse{
|
|
DecryptedValue: val,
|
|
Exists: ok,
|
|
}, nil
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) SetSecret(ctx context.Context, in *secretsmanagerplugin.SetSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.SetSecretResponse, error) {
|
|
c.kv[buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)] = in.Value
|
|
return &secretsmanagerplugin.SetSecretResponse{}, nil
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) DeleteSecret(ctx context.Context, in *secretsmanagerplugin.DeleteSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.DeleteSecretResponse, error) {
|
|
delete(c.kv, buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type))
|
|
return &secretsmanagerplugin.DeleteSecretResponse{}, nil
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) ListSecrets(ctx context.Context, in *secretsmanagerplugin.ListSecretsRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.ListSecretsResponse, error) {
|
|
res := make([]*secretsmanagerplugin.Key, 0)
|
|
for k := range c.kv {
|
|
if in.KeyDescriptor.OrgId == AllOrganizations && in.KeyDescriptor.Namespace == "" && in.KeyDescriptor.Type == "" {
|
|
res = append(res, internalToProtoKey(k))
|
|
} else if k.OrgId == in.KeyDescriptor.OrgId && k.Namespace == in.KeyDescriptor.Namespace && k.Type == in.KeyDescriptor.Type {
|
|
res = append(res, internalToProtoKey(k))
|
|
}
|
|
}
|
|
return &secretsmanagerplugin.ListSecretsResponse{
|
|
Keys: res,
|
|
}, nil
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) RenameSecret(ctx context.Context, in *secretsmanagerplugin.RenameSecretRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.RenameSecretResponse, error) {
|
|
oldKey := buildKey(in.KeyDescriptor.OrgId, in.KeyDescriptor.Namespace, in.KeyDescriptor.Type)
|
|
val := c.kv[oldKey]
|
|
delete(c.kv, oldKey)
|
|
c.kv[buildKey(in.KeyDescriptor.OrgId, in.NewNamespace, in.KeyDescriptor.Type)] = val
|
|
return &secretsmanagerplugin.RenameSecretResponse{}, nil
|
|
}
|
|
|
|
func (c *fakeGRPCSecretsPlugin) GetAllSecrets(ctx context.Context, in *secretsmanagerplugin.GetAllSecretsRequest, opts ...grpc.CallOption) (*secretsmanagerplugin.GetAllSecretsResponse, error) {
|
|
items := make([]*secretsmanagerplugin.Item, 0)
|
|
for k, v := range c.kv {
|
|
items = append(items, &secretsmanagerplugin.Item{
|
|
Key: internalToProtoKey(k),
|
|
Value: v,
|
|
})
|
|
}
|
|
return &secretsmanagerplugin.GetAllSecretsResponse{
|
|
Items: items,
|
|
}, nil
|
|
}
|
|
|
|
var _ SecretsKVStore = &FakeSecretsKVStore{}
|
|
var _ secretsmanagerplugin.SecretsManagerPlugin = &fakeGRPCSecretsPlugin{}
|
|
|
|
// Fake plugin manager
|
|
type fakePluginManager struct {
|
|
shouldFailOnStart bool
|
|
plugin *plugins.Plugin
|
|
}
|
|
|
|
func (mg *fakePluginManager) SecretsManager(_ context.Context) *plugins.Plugin {
|
|
if mg.plugin != nil {
|
|
return mg.plugin
|
|
}
|
|
p := &plugins.Plugin{
|
|
SecretsManager: &fakeGRPCSecretsPlugin{
|
|
kv: make(map[Key]string),
|
|
},
|
|
}
|
|
p.RegisterClient(&fakePluginClient{
|
|
shouldFailOnStart: mg.shouldFailOnStart,
|
|
})
|
|
mg.plugin = p
|
|
return p
|
|
}
|
|
|
|
func NewFakeSecretsPluginManager(t *testing.T, shouldFailOnStart bool) plugins.SecretsPluginManager {
|
|
t.Helper()
|
|
return &fakePluginManager{
|
|
shouldFailOnStart: shouldFailOnStart,
|
|
}
|
|
}
|
|
|
|
// Fake plugin client
|
|
type fakePluginClient struct {
|
|
shouldFailOnStart bool
|
|
backendplugin.Plugin
|
|
}
|
|
|
|
func (pc *fakePluginClient) Start(_ context.Context) error {
|
|
if pc.shouldFailOnStart {
|
|
return errors.New("mocked failed to start")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (pc *fakePluginClient) Stop(_ context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (pc *fakePluginClient) Logger() pluginsLogger.Logger {
|
|
return pluginsLogger.NewTestLogger()
|
|
}
|
|
|
|
func SetupFatalCrashTest(
|
|
t *testing.T,
|
|
shouldFailOnStart bool,
|
|
isPluginErrorFatal bool,
|
|
isBackwardsCompatDisabled bool,
|
|
) (fatalCrashTestFields, error) {
|
|
t.Helper()
|
|
fatalFlagOnce = sync.Once{}
|
|
startupOnce = sync.Once{}
|
|
cfg := SetupTestConfig(t)
|
|
sqlStore := db.InitTestDB(t)
|
|
secretService := fakes.FakeSecretsService{}
|
|
kvstore := kvstore.ProvideService(sqlStore)
|
|
if isPluginErrorFatal {
|
|
_ = SetPluginStartupErrorFatal(context.Background(), GetNamespacedKVStore(kvstore), true)
|
|
}
|
|
features := NewFakeFeatureToggles(t, isBackwardsCompatDisabled)
|
|
manager := NewFakeSecretsPluginManager(t, shouldFailOnStart)
|
|
svc, err := ProvideService(sqlStore, secretService, manager, kvstore, features, cfg)
|
|
t.Cleanup(ResetPlugin)
|
|
return fatalCrashTestFields{
|
|
SecretsKVStore: svc,
|
|
PluginManager: manager,
|
|
KVStore: kvstore,
|
|
SqlStore: sqlStore,
|
|
}, err
|
|
}
|
|
|
|
type fatalCrashTestFields struct {
|
|
SecretsKVStore SecretsKVStore
|
|
PluginManager plugins.SecretsPluginManager
|
|
KVStore kvstore.KVStore
|
|
SqlStore db.DB
|
|
}
|
|
|
|
func SetupTestConfig(t *testing.T) *setting.Cfg {
|
|
t.Helper()
|
|
rawCfg := `
|
|
[secrets]
|
|
use_plugin = true
|
|
`
|
|
raw, err := ini.Load([]byte(rawCfg))
|
|
require.NoError(t, err)
|
|
return &setting.Cfg{Raw: raw}
|
|
}
|
|
|
|
func ReplaceFallback(t *testing.T, kv SecretsKVStore, fb SecretsKVStore) error {
|
|
t.Helper()
|
|
if store, ok := kv.(*CachedKVStore); ok {
|
|
kv = store.store
|
|
}
|
|
if store, ok := kv.(*SecretsKVStorePlugin); ok {
|
|
store.fallbackStore = fb
|
|
return nil
|
|
}
|
|
return errors.New("not a plugin store")
|
|
}
|