grafana/pkg/services/secrets/kvstore/remote_plugin.go
Michael Mandrus c043a8818a
Secrets: add better error handling for secret plugin failures when updating datasources (#50542)
* 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

* ensure atomicity by adding secret management as a step to sql operations and rolling back if necessary

* 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

* add special error handling for remote secrets management

* switch to errors.as instead of type inference

* remove unnecessary rollback call

* just declare error once

* refactor .proto file according to prior PR suggestions

* re-generate protobuf files and fix compilation errors

* only wrap (ergo display in the front end) errors that are user friendly from the plugin

* rename error type to suggest user friendly only

* rename plugin functions to be more descriptive

* change delete message name

* Revert "change delete message name"

This reverts commit 8ca978301e.

* Revert "rename plugin functions to be more descriptive"

This reverts commit 4355c9b9ff.

* fix pointer to pointer problem

* change plugin user error to just hold a string

* fix sequencing problem with datasource updates

* clean up some return statements

* need to wrap multiple transactions with the InTransaction() func in order to keep the lock

* make linter happy

* revert input var name

Co-authored-by: Marcus Efraimsson <marcus.efraimsson@gmail.com>
2022-06-16 12:26:57 -04:00

130 lines
3.4 KiB
Go

package kvstore
import (
"context"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
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.GetSecretRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
}
res, err := kv.secretsPlugin.GetSecret(ctx, req)
if err != nil {
return "", false, err
} else if res.UserFriendlyError != "" {
err = wrapUserFriendlySecretError(res.UserFriendlyError)
}
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.SetSecretRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
Value: value,
}
res, err := kv.secretsPlugin.SetSecret(ctx, req)
if err == nil && res.UserFriendlyError != "" {
err = wrapUserFriendlySecretError(res.UserFriendlyError)
}
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.DeleteSecretRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
}
res, err := kv.secretsPlugin.DeleteSecret(ctx, req)
if err == nil && res.UserFriendlyError != "" {
err = wrapUserFriendlySecretError(res.UserFriendlyError)
}
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.ListSecretsRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
AllOrganizations: orgId == AllOrganizations,
}
res, err := kv.secretsPlugin.ListSecrets(ctx, req)
if err != nil {
return nil, err
} else if res.UserFriendlyError != "" {
err = wrapUserFriendlySecretError(res.UserFriendlyError)
}
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.RenameSecretRequest{
KeyDescriptor: &smp.Key{
OrgId: orgId,
Namespace: namespace,
Type: typ,
},
NewNamespace: newNamespace,
}
res, err := kv.secretsPlugin.RenameSecret(ctx, req)
if err == nil && res.UserFriendlyError != "" {
err = wrapUserFriendlySecretError(res.UserFriendlyError)
}
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
}
func wrapUserFriendlySecretError(ufe string) models.ErrDatasourceSecretsPluginUserFriendly {
return models.ErrDatasourceSecretsPluginUserFriendly{Err: ufe}
}