WIP: Add private Secret Manager Plugins support to plugin platform (#49544)

* Add protobuf config and generated code, and client wrapper

* wire up loading of secretsmanager plugin, using renderer plugin as a model

* update kvstore provider to check if we should use the grpc plugin. return false always in OSS

* add OSS remote plugin check

* refactor wire gen file

* log which secrets manager is being used

* Fix argument types for remote checker

* Turns out if err != nil, then the result is always nil. Return empty values if there is an error.

* remove duplicate import

* Update pkg/services/secrets/kvstore/kvstore.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* Update pkg/services/secrets/kvstore/kvstore.go

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>

* refactor RemotePluginCheck interface to just return the Plugin client directly

* rename struct to something less silly

* Update pkg/plugins/backendplugin/secretsmanagerplugin/secretsmanager.proto

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Michael Mandrus
2022-06-09 13:19:27 -04:00
committed by GitHub
co-authored by Marcus Efraimsson Will Browne
parent ef401f5d62
commit f376c33903
17 changed files with 1460 additions and 23 deletions
+17 -2
View File
@@ -13,11 +13,26 @@ const (
AllOrganizations = -1
)
func ProvideService(sqlStore sqlstore.Store, secretsService secrets.Service) SecretsKVStore {
func ProvideService(sqlStore sqlstore.Store, secretsService secrets.Service, remoteCheck UseRemoteSecretsPluginCheck) SecretsKVStore {
logger := log.New("secrets.kvstore")
if remoteCheck.ShouldUseRemoteSecretsPlugin() {
logger.Debug("secrets kvstore is using a remote plugin for secrets management")
secretsPlugin, err := remoteCheck.GetPlugin()
if err != nil {
logger.Error("plugin client was nil, falling back to SQL implementation")
} else {
return &secretsKVStorePlugin{
secretsPlugin: secretsPlugin,
secretsService: secretsService,
log: logger,
}
}
}
logger.Debug("secrets kvstore is using the default (SQL) implementation for secrets management")
return &secretsKVStoreSQL{
sqlStore: sqlStore,
secretsService: secretsService,
log: log.New("secrets.kvstore"),
log: logger,
decryptionCache: decryptionCache{
cache: make(map[int64]cachedDecrypted),
},
@@ -0,0 +1,125 @@
package kvstore
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/infra/log"
smp "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
"github.com/grafana/grafana/pkg/services/secrets"
)
// secretsKVStorePlugin provides a key/value store backed by the Grafana plugin gRPC interface
type secretsKVStorePlugin struct {
log log.Logger
secretsPlugin smp.SecretsManagerPlugin
secretsService secrets.Service
}
// Get an item from the store
func (kv *secretsKVStorePlugin) Get(ctx context.Context, orgId int64, namespace string, typ string) (string, bool, error) {
req := &smp.SecretsGetRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
}
res, err := kv.secretsPlugin.Get(ctx, req)
if err != nil {
return "", false, err
} else if res.Error != "" {
err = fmt.Errorf(res.Error)
}
return res.DecryptedValue, res.Exists, err
}
// Set an item in the store
func (kv *secretsKVStorePlugin) Set(ctx context.Context, orgId int64, namespace string, typ string, value string) error {
req := &smp.SecretsSetRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
Value: value,
}
res, err := kv.secretsPlugin.Set(ctx, req)
if err == nil && res.Error != "" {
err = fmt.Errorf(res.Error)
}
return err
}
// Del deletes an item from the store.
func (kv *secretsKVStorePlugin) Del(ctx context.Context, orgId int64, namespace string, typ string) error {
req := &smp.SecretsDelRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
}
res, err := kv.secretsPlugin.Del(ctx, req)
if err == nil && res.Error != "" {
err = fmt.Errorf(res.Error)
}
return err
}
// Keys get all keys for a given namespace. To query for all
// organizations the constant 'kvstore.AllOrganizations' can be passed as orgId.
func (kv *secretsKVStorePlugin) Keys(ctx context.Context, orgId int64, namespace string, typ string) ([]Key, error) {
req := &smp.SecretsKeysRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
AllOrganizations: orgId == AllOrganizations,
}
res, err := kv.secretsPlugin.Keys(ctx, req)
if err != nil {
return nil, err
} else if res.Error != "" {
err = fmt.Errorf(res.Error)
}
return parseKeys(res.Keys), err
}
// Rename an item in the store
func (kv *secretsKVStorePlugin) Rename(ctx context.Context, orgId int64, namespace string, typ string, newNamespace string) error {
req := &smp.SecretsRenameRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
NewNamespace: newNamespace,
}
res, err := kv.secretsPlugin.Rename(ctx, req)
if err == nil && res.Error != "" {
err = fmt.Errorf(res.Error)
}
return err
}
func parseKeys(keys []*smp.Key) []Key {
var newKeys []Key
for _, k := range keys {
newKey := Key{OrgId: k.OrgId, Namespace: k.Namespace, Type: k.Type}
newKeys = append(newKeys, newKey)
}
return newKeys
}
@@ -0,0 +1,26 @@
package kvstore
import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
)
type UseRemoteSecretsPluginCheck interface {
ShouldUseRemoteSecretsPlugin() bool
GetPlugin() (secretsmanagerplugin.SecretsManagerPlugin, error)
}
type OSSRemoteSecretsPluginCheck struct {
UseRemoteSecretsPluginCheck
}
func ProvideRemotePluginCheck() *OSSRemoteSecretsPluginCheck {
return &OSSRemoteSecretsPluginCheck{}
}
func (c *OSSRemoteSecretsPluginCheck) ShouldUseRemoteSecretsPlugin() bool {
return false
}
func (c *OSSRemoteSecretsPluginCheck) GetPlugin() (secretsmanagerplugin.SecretsManagerPlugin, error) {
return nil, nil
}