2022-06-09 12:19:27 -05:00
|
|
|
package kvstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-07-25 11:37:47 -05:00
|
|
|
"sync"
|
2022-06-09 12:19:27 -05:00
|
|
|
|
2022-07-25 11:37:47 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
2022-06-09 12:19:27 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
|
|
smp "github.com/grafana/grafana/pkg/plugins/backendplugin/secretsmanagerplugin"
|
2022-06-27 11:23:15 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/datasources"
|
2022-06-09 12:19:27 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/secrets"
|
|
|
|
)
|
|
|
|
|
2022-07-25 11:37:47 -05:00
|
|
|
var (
|
|
|
|
fatalFlagOnce sync.Once
|
|
|
|
)
|
|
|
|
|
2022-06-09 12:19:27 -05:00
|
|
|
// secretsKVStorePlugin provides a key/value store backed by the Grafana plugin gRPC interface
|
|
|
|
type secretsKVStorePlugin struct {
|
2022-07-25 11:37:47 -05:00
|
|
|
log log.Logger
|
|
|
|
secretsPlugin smp.SecretsManagerPlugin
|
|
|
|
secretsService secrets.Service
|
|
|
|
kvstore *kvstore.NamespacedKVStore
|
|
|
|
backwardsCompatibilityDisabled bool
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get an item from the store
|
2022-07-25 11:37:47 -05:00
|
|
|
// If it is the first time a secret has been retrieved and backwards compatibility is disabled, mark plugin startup errors fatal
|
2022-06-09 12:19:27 -05:00
|
|
|
func (kv *secretsKVStorePlugin) Get(ctx context.Context, orgId int64, namespace string, typ string) (string, bool, error) {
|
2022-06-14 10:53:04 -05:00
|
|
|
req := &smp.GetSecretRequest{
|
2022-06-09 12:19:27 -05:00
|
|
|
KeyDescriptor: &smp.Key{
|
|
|
|
OrgId: orgId,
|
|
|
|
Namespace: namespace,
|
|
|
|
Type: typ,
|
|
|
|
},
|
|
|
|
}
|
2022-06-14 10:53:04 -05:00
|
|
|
res, err := kv.secretsPlugin.GetSecret(ctx, req)
|
2022-06-09 12:19:27 -05:00
|
|
|
if err != nil {
|
|
|
|
return "", false, err
|
2022-06-14 10:53:04 -05:00
|
|
|
} else if res.UserFriendlyError != "" {
|
2022-06-16 11:26:57 -05:00
|
|
|
err = wrapUserFriendlySecretError(res.UserFriendlyError)
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
2022-07-25 11:37:47 -05:00
|
|
|
if res.Exists {
|
|
|
|
updateFatalFlag(ctx, *kv)
|
|
|
|
}
|
|
|
|
|
2022-06-09 12:19:27 -05:00
|
|
|
return res.DecryptedValue, res.Exists, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set an item in the store
|
2022-07-25 11:37:47 -05:00
|
|
|
// If it is the first time a secret has been set and backwards compatibility is disabled, mark plugin startup errors fatal
|
2022-06-09 12:19:27 -05:00
|
|
|
func (kv *secretsKVStorePlugin) Set(ctx context.Context, orgId int64, namespace string, typ string, value string) error {
|
2022-06-14 10:53:04 -05:00
|
|
|
req := &smp.SetSecretRequest{
|
2022-06-09 12:19:27 -05:00
|
|
|
KeyDescriptor: &smp.Key{
|
|
|
|
OrgId: orgId,
|
|
|
|
Namespace: namespace,
|
|
|
|
Type: typ,
|
|
|
|
},
|
|
|
|
Value: value,
|
|
|
|
}
|
|
|
|
|
2022-06-14 10:53:04 -05:00
|
|
|
res, err := kv.secretsPlugin.SetSecret(ctx, req)
|
|
|
|
if err == nil && res.UserFriendlyError != "" {
|
2022-06-16 11:26:57 -05:00
|
|
|
err = wrapUserFriendlySecretError(res.UserFriendlyError)
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
2022-07-25 11:37:47 -05:00
|
|
|
updateFatalFlag(ctx, *kv)
|
|
|
|
|
2022-06-09 12:19:27 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Del deletes an item from the store.
|
|
|
|
func (kv *secretsKVStorePlugin) Del(ctx context.Context, orgId int64, namespace string, typ string) error {
|
2022-06-14 10:53:04 -05:00
|
|
|
req := &smp.DeleteSecretRequest{
|
2022-06-09 12:19:27 -05:00
|
|
|
KeyDescriptor: &smp.Key{
|
|
|
|
OrgId: orgId,
|
|
|
|
Namespace: namespace,
|
|
|
|
Type: typ,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-06-14 10:53:04 -05:00
|
|
|
res, err := kv.secretsPlugin.DeleteSecret(ctx, req)
|
|
|
|
if err == nil && res.UserFriendlyError != "" {
|
2022-06-16 11:26:57 -05:00
|
|
|
err = wrapUserFriendlySecretError(res.UserFriendlyError)
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2022-06-14 10:53:04 -05:00
|
|
|
req := &smp.ListSecretsRequest{
|
2022-06-09 12:19:27 -05:00
|
|
|
KeyDescriptor: &smp.Key{
|
|
|
|
OrgId: orgId,
|
|
|
|
Namespace: namespace,
|
|
|
|
Type: typ,
|
|
|
|
},
|
|
|
|
AllOrganizations: orgId == AllOrganizations,
|
|
|
|
}
|
|
|
|
|
2022-06-14 10:53:04 -05:00
|
|
|
res, err := kv.secretsPlugin.ListSecrets(ctx, req)
|
2022-06-09 12:19:27 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2022-06-14 10:53:04 -05:00
|
|
|
} else if res.UserFriendlyError != "" {
|
2022-06-16 11:26:57 -05:00
|
|
|
err = wrapUserFriendlySecretError(res.UserFriendlyError)
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2022-06-14 10:53:04 -05:00
|
|
|
req := &smp.RenameSecretRequest{
|
2022-06-09 12:19:27 -05:00
|
|
|
KeyDescriptor: &smp.Key{
|
|
|
|
OrgId: orgId,
|
|
|
|
Namespace: namespace,
|
|
|
|
Type: typ,
|
|
|
|
},
|
|
|
|
NewNamespace: newNamespace,
|
|
|
|
}
|
|
|
|
|
2022-06-14 10:53:04 -05:00
|
|
|
res, err := kv.secretsPlugin.RenameSecret(ctx, req)
|
|
|
|
if err == nil && res.UserFriendlyError != "" {
|
2022-06-16 11:26:57 -05:00
|
|
|
err = wrapUserFriendlySecretError(res.UserFriendlyError)
|
2022-06-09 12:19:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2022-06-16 11:26:57 -05:00
|
|
|
|
2022-07-25 11:37:47 -05:00
|
|
|
func updateFatalFlag(ctx context.Context, skv secretsKVStorePlugin) {
|
|
|
|
// This function makes the most sense in here because it handles all possible scenarios:
|
|
|
|
// - User changed backwards compatibility flag, so we have to migrate secrets either to or from the plugin (get or set)
|
|
|
|
// - Migration is on, so we migrate secrets to the plugin (set)
|
|
|
|
// - User doesn't migrate, but stores a new secret in the plugin (set)
|
|
|
|
// Rather than updating the flag in several places, it is cleaner to just do this check once
|
|
|
|
// Very early on. Once backwards compatibility to legacy secrets is gone in Grafana 10, this can go away as well
|
|
|
|
fatalFlagOnce.Do(func() {
|
|
|
|
var err error
|
|
|
|
if isFatal, _ := isPluginStartupErrorFatal(ctx, skv.kvstore); !isFatal && skv.backwardsCompatibilityDisabled {
|
|
|
|
err = setPluginStartupErrorFatal(ctx, skv.kvstore, true)
|
|
|
|
} else if isFatal && !skv.backwardsCompatibilityDisabled {
|
|
|
|
err = setPluginStartupErrorFatal(ctx, skv.kvstore, false)
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
skv.log.Error("failed to set plugin error fatal flag", err.Error())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-06-27 11:23:15 -05:00
|
|
|
func wrapUserFriendlySecretError(ufe string) datasources.ErrDatasourceSecretsPluginUserFriendly {
|
|
|
|
return datasources.ErrDatasourceSecretsPluginUserFriendly{Err: ufe}
|
2022-06-16 11:26:57 -05:00
|
|
|
}
|