SecretsManager: Keeper and secure value contracts, secretkeeper changes (#105379)

Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com>
Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
This commit is contained in:
Dana Axinte
2025-05-14 15:24:25 +01:00
committed by GitHub
co-authored by PoorlyDefinedBehaviour Matheus Macabu
parent 137639e0de
commit 8c64078965
10 changed files with 204 additions and 83 deletions
+70 -17
View File
@@ -6,7 +6,7 @@ import (
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
"k8s.io/apimachinery/pkg/util/validation/field"
)
var (
@@ -15,23 +15,74 @@ var (
// KeeperMetadataStorage is the interface for wiring and dependency injection.
type KeeperMetadataStorage interface {
Create(ctx context.Context, keeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error)
Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.Keeper, error)
Update(ctx context.Context, keeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error)
Create(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error)
Read(ctx context.Context, namespace xkube.Namespace, name string, opts ReadOpts) (*secretv0alpha1.Keeper, error)
Update(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error)
Delete(ctx context.Context, namespace xkube.Namespace, name string) error
List(ctx context.Context, namespace xkube.Namespace, options *internalversion.ListOptions) (*secretv0alpha1.KeeperList, error)
List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.Keeper, error)
GetKeeperConfig(ctx context.Context, namespace string, name *string, opts ReadOpts) (secretv0alpha1.KeeperConfig, error)
}
// KeeperType represents the type of a Keeper.
type KeeperType string
// ErrKeeperInvalidSecureValues is returned when a Keeper references SecureValues that do not exist.
type ErrKeeperInvalidSecureValues struct {
invalidSecureValues map[string]struct{}
}
const (
SQLKeeperType KeeperType = "sql"
AWSKeeperType KeeperType = "aws"
AzureKeeperType KeeperType = "azure"
GCPKeeperType KeeperType = "gcp"
HashiCorpKeeperType KeeperType = "hashicorp"
)
var _ xkube.ErrorLister = (*ErrKeeperInvalidSecureValues)(nil)
func NewErrKeeperInvalidSecureValues(invalidSecureValues map[string]struct{}) *ErrKeeperInvalidSecureValues {
return &ErrKeeperInvalidSecureValues{invalidSecureValues: invalidSecureValues}
}
func (e *ErrKeeperInvalidSecureValues) Error() string {
return e.ErrorList().ToAggregate().Error()
}
func (e *ErrKeeperInvalidSecureValues) ErrorList() field.ErrorList {
errs := make(field.ErrorList, 0, len(e.invalidSecureValues))
path := field.NewPath("secureValueName")
for sv := range e.invalidSecureValues {
errs = append(errs, field.NotFound(path, sv))
}
return errs
}
// ErrKeeperInvalidSecureValuesReference is returned when a Keeper references SecureValues from a non-SQL Keeper.
type ErrKeeperInvalidSecureValuesReference struct {
invalidSecureValues map[string]string
}
var _ xkube.ErrorLister = (*ErrKeeperInvalidSecureValuesReference)(nil)
func NewErrKeeperInvalidSecureValuesReference(invalidSecureValues map[string]string) *ErrKeeperInvalidSecureValuesReference {
return &ErrKeeperInvalidSecureValuesReference{invalidSecureValues: invalidSecureValues}
}
func (e *ErrKeeperInvalidSecureValuesReference) Error() string {
return e.ErrorList().ToAggregate().Error()
}
func (e *ErrKeeperInvalidSecureValuesReference) ErrorList() field.ErrorList {
errs := make(field.ErrorList, 0, len(e.invalidSecureValues))
path := field.NewPath("secureValueName")
for sv, keeper := range e.invalidSecureValues {
errs = append(
errs,
field.TypeInvalid(
path,
sv,
`cannot reference third-party keeper "`+keeper+`" in another third-party keeper, use a "sql" keeper instead`,
),
)
}
return errs
}
// ExternalID represents either the secure value's GUID or ref (in case of external secret references).
// This is saved in the secure_value metadata storage as `external_id`.
@@ -50,6 +101,8 @@ type Keeper interface {
Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID ExternalID) error
}
const (
DefaultSQLKeeper = "kp-default-sql"
)
// Service is the interface for secret keeper services.
// This exists because OSS and Enterprise have different amounts of keepers available.
type KeeperService interface {
KeeperForConfig(secretv0alpha1.KeeperConfig) (Keeper, error)
}
@@ -6,18 +6,33 @@ import (
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
)
type DecryptSecureValue struct {
Keeper *string
Ref string
ExternalID string
Decrypters []string
}
var (
ErrSecureValueNotFound = errors.New("secure value not found")
ErrSecureValueNotFound = errors.New("secure value not found")
ErrSecureValueAlreadyExists = errors.New("secure value already exists")
ErrSecureValueOperationInProgress = errors.New("an operation is already in progress for the secure value")
)
type ReadOpts struct {
ForUpdate bool
}
// SecureValueMetadataStorage is the interface for wiring and dependency injection.
type SecureValueMetadataStorage interface {
Create(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error)
Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error)
Update(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error)
Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error)
Read(ctx context.Context, namespace xkube.Namespace, name string, opts ReadOpts) (*secretv0alpha1.SecureValue, error)
Update(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error)
Delete(ctx context.Context, namespace xkube.Namespace, name string) error
List(ctx context.Context, namespace xkube.Namespace, options *internalversion.ListOptions) (*secretv0alpha1.SecureValueList, error)
List(ctx context.Context, namespace xkube.Namespace) (*secretv0alpha1.SecureValueList, error)
SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error
SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID ExternalID) error
ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*DecryptSecureValue, error)
}
@@ -9,6 +9,7 @@ import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
@@ -79,12 +80,27 @@ func (s *KeeperRest) List(ctx context.Context, options *internalversion.ListOpti
return nil, fmt.Errorf("missing namespace")
}
keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace), options)
labelSelector := options.LabelSelector
if labelSelector == nil {
labelSelector = labels.Everything()
}
keepersList, err := s.storage.List(ctx, xkube.Namespace(namespace))
if err != nil {
return nil, fmt.Errorf("failed to list keepers: %w", err)
}
return keepersList, nil
allowedKeepers := make([]secretv0alpha1.Keeper, 0)
for _, keeper := range keepersList {
if labelSelector.Matches(labels.Set(keeper.Labels)) {
allowedKeepers = append(allowedKeepers, keeper)
}
}
return &secretv0alpha1.KeeperList{
Items: allowedKeepers,
}, nil
}
// Get calls the inner `store` (persistence) and returns a `Keeper` by `name`.
@@ -94,7 +110,8 @@ func (s *KeeperRest) Get(ctx context.Context, name string, options *metav1.GetOp
return nil, fmt.Errorf("missing namespace")
}
kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name)
// TODO: readopts
kp, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{})
if err != nil {
if errors.Is(err, contracts.ErrKeeperNotFound) {
return nil, s.resource.NewNotFound(name)
@@ -121,7 +138,7 @@ func (s *KeeperRest) Create(
return nil, err
}
createdKeeper, err := s.storage.Create(ctx, kp)
createdKeeper, err := s.storage.Create(ctx, kp, "todo-user")
if err != nil {
var kErr xkube.ErrorLister
if errors.As(err, &kErr) {
@@ -172,7 +189,7 @@ func (s *KeeperRest) Update(
newKeeper.Annotations = xkube.CleanAnnotations(newKeeper.Annotations)
// Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that?
updatedKeeper, err := s.storage.Update(ctx, newKeeper)
updatedKeeper, err := s.storage.Update(ctx, newKeeper, "todo-user")
if err != nil {
var kErr xkube.ErrorLister
if errors.As(err, &kErr) {
@@ -193,7 +210,11 @@ func (s *KeeperRest) Delete(ctx context.Context, name string, deleteValidation r
return nil, false, fmt.Errorf("missing namespace")
}
if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil {
err := s.storage.Delete(ctx, xkube.Namespace(namespace), name)
if err != nil {
if errors.Is(err, contracts.ErrKeeperNotFound) {
return nil, false, s.resource.NewNotFound(name)
}
return nil, false, fmt.Errorf("failed to delete keeper: %w", err)
}
@@ -9,6 +9,8 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
@@ -79,12 +81,35 @@ func (s *SecureValueRest) List(ctx context.Context, options *internalversion.Lis
return nil, fmt.Errorf("missing namespace")
}
secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace), options)
secureValueList, err := s.storage.List(ctx, xkube.Namespace(namespace))
if err != nil {
return nil, fmt.Errorf("failed to list secure values: %w", err)
}
return secureValueList, nil
labelSelector := options.LabelSelector
if labelSelector == nil {
labelSelector = labels.Everything()
}
fieldSelector := options.FieldSelector
if fieldSelector == nil {
fieldSelector = fields.Everything()
}
allowedSecureValues := make([]secretv0alpha1.SecureValue, 0, len(secureValueList.Items))
for _, secureValue := range secureValueList.Items {
// Filter by label
if labelSelector.Matches(labels.Set(secureValue.Labels)) {
// Filter by status.phase
if fieldSelector.Matches(fields.Set{"status.phase": string(secureValue.Status.Phase)}) {
allowedSecureValues = append(allowedSecureValues, secureValue)
}
}
}
return &secretv0alpha1.SecureValueList{Items: allowedSecureValues}, nil
}
// Get calls the inner `store` (persistence) and returns a `securevalue` by `name`. It will NOT return the decrypted `value`.
@@ -94,7 +119,7 @@ func (s *SecureValueRest) Get(ctx context.Context, name string, options *metav1.
return nil, fmt.Errorf("missing namespace")
}
sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name)
sv, err := s.storage.Read(ctx, xkube.Namespace(namespace), name, contracts.ReadOpts{})
if err != nil {
if errors.Is(err, contracts.ErrSecureValueNotFound) {
return nil, s.resource.NewNotFound(name)
@@ -122,7 +147,7 @@ func (s *SecureValueRest) Create(
return nil, err
}
createdSecureValue, err := s.storage.Create(ctx, sv)
createdSecureValue, err := s.storage.Create(ctx, sv, "todo-user")
if err != nil {
return nil, fmt.Errorf("failed to create secure value: %w", err)
}
@@ -166,7 +191,7 @@ func (s *SecureValueRest) Update(
newSecureValue.Annotations = xkube.CleanAnnotations(newSecureValue.Annotations)
// Current implementation replaces everything passed in the spec, so it is not a PATCH. Do we want/need to support that?
updatedSecureValue, err := s.storage.Update(ctx, newSecureValue)
updatedSecureValue, err := s.storage.Update(ctx, newSecureValue, "todo-user")
if err != nil {
return nil, false, fmt.Errorf("failed to update secure value: %w", err)
}
@@ -176,17 +201,20 @@ func (s *SecureValueRest) Update(
// Delete calls the inner `store` (persistence) in order to delete the `securevalue`.
// The second return parameter `bool` indicates whether the delete was instant or not. It always is for `securevalues`.
func (s *SecureValueRest) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
func (s *SecureValueRest) Delete(ctx context.Context, name string, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions) (runtime.Object, bool, error) {
namespace, ok := request.NamespaceFrom(ctx)
if !ok {
return nil, false, fmt.Errorf("missing namespace")
}
if err := s.storage.Delete(ctx, xkube.Namespace(namespace), name); err != nil {
return nil, false, fmt.Errorf("delete secure value: %w", err)
if errors.Is(err, contracts.ErrSecureValueNotFound) {
return nil, false, s.resource.NewNotFound(name)
}
return nil, false, fmt.Errorf("deleting secure value: %+w", err)
}
return nil, true, nil
return nil, false, nil
}
// ValidateSecureValue does basic spec validation of a securevalue.
@@ -1,45 +1,32 @@
package secretkeeper
import (
"fmt"
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
)
// Service is the interface for secret keeper services.
// This exists because OSS and Enterprise have different amounts of keepers available.
type Service interface {
GetKeepers() (map[contracts.KeeperType]contracts.Keeper, error)
}
// OSSKeeperService is the OSS implementation of the Service interface.
type OSSKeeperService struct {
tracer tracing.Tracer
encryptionManager contracts.EncryptionManager
store contracts.EncryptedValueStorage
systemKeeper *sqlkeeper.SQLKeeper
}
var _ contracts.KeeperService = (*OSSKeeperService)(nil)
func ProvideService(
tracer tracing.Tracer,
store contracts.EncryptedValueStorage,
encryptionManager contracts.EncryptionManager,
) (OSSKeeperService, error) {
return OSSKeeperService{
tracer: tracer,
encryptionManager: encryptionManager,
store: store,
) (*OSSKeeperService, error) {
return &OSSKeeperService{
// TODO: rename to system keeper or something like that
systemKeeper: sqlkeeper.NewSQLKeeper(tracer, encryptionManager, store),
}, nil
}
func (ks OSSKeeperService) GetKeepers() (map[contracts.KeeperType]contracts.Keeper, error) {
sqlKeeper, err := sqlkeeper.NewSQLKeeper(ks.tracer, ks.encryptionManager, ks.store)
if err != nil {
return nil, fmt.Errorf("failed to create sql keeper: %w", err)
}
return map[contracts.KeeperType]contracts.Keeper{
contracts.SQLKeeperType: sqlKeeper,
}, nil
// Ignore the config, but we could use it to get the keeper type and then return the correct keeper.
// Instantiation only happens on ProvideService ONCE.
func (k *OSSKeeperService) KeeperForConfig(secretv0alpha1.KeeperConfig) (contracts.Keeper, error) {
return k.systemKeeper, nil
}
@@ -7,27 +7,30 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/testsuite"
)
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func Test_OSSKeeperService_GetKeepers(t *testing.T) {
cfg := setting.NewCfg()
keeperService, err := setupTestService(t, cfg)
require.NoError(t, err)
t.Run("GetKeepers should return a map with a sql keeper", func(t *testing.T) {
keeperMap, err := keeperService.GetKeepers()
t.Run("KeeperForConfig should return the system keeper", func(t *testing.T) {
keeper, err := keeperService.KeeperForConfig(nil)
require.NoError(t, err)
assert.NotNil(t, keeperMap)
assert.Equal(t, 1, len(keeperMap))
assert.IsType(t, &sqlkeeper.SQLKeeper{}, keeperMap[contracts.SQLKeeperType])
assert.NotNil(t, keeper)
assert.IsType(t, &sqlkeeper.SQLKeeper{}, keeper)
})
}
func setupTestService(t *testing.T, cfg *setting.Cfg) (OSSKeeperService, error) {
func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error) {
// Initialize the keeper service
keeperService, err := ProvideService(tracing.InitializeTracerForTest(), nil, nil)
@@ -21,12 +21,12 @@ func NewSQLKeeper(
tracer tracing.Tracer,
encryptionManager contracts.EncryptionManager,
store contracts.EncryptedValueStorage,
) (*SQLKeeper, error) {
) *SQLKeeper {
return &SQLKeeper{
tracer: tracer,
encryptionManager: encryptionManager,
store: store,
}, nil
}
}
func (s *SQLKeeper) Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) {
@@ -143,9 +143,9 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) {
encValueStore := newInMemoryEncryptedValueStorage()
// Initialize the SQLKeeper
sqlKeeper, err := NewSQLKeeper(tracing.InitializeTracerForTest(), encMgr, encValueStore)
sqlKeeper := NewSQLKeeper(tracing.InitializeTracerForTest(), encMgr, encValueStore)
return sqlKeeper, err
return sqlKeeper, nil
}
// While we don't have the real implementation, use an in-memory one
+8 -5
View File
@@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
)
func ProvideKeeperMetadataStorage(db db.DB, features featuremgmt.FeatureToggles, accessClient claims.AccessClient) (contracts.KeeperMetadataStorage, error) {
@@ -27,15 +26,15 @@ type keeperMetadataStorage struct {
accessClient claims.AccessClient
}
func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error) {
func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) {
return nil, nil
}
func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.Keeper, error) {
func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.Keeper, error) {
return nil, nil
}
func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0alpha1.Keeper) (*secretv0alpha1.Keeper, error) {
func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) {
return nil, nil
}
@@ -43,6 +42,10 @@ func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Name
return nil
}
func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace, options *internalversion.ListOptions) (*secretv0alpha1.KeeperList, error) {
func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) ([]secretv0alpha1.Keeper, error) {
return nil, nil
}
func (s *keeperMetadataStorage) GetKeeperConfig(ctx context.Context, namespace string, name *string, opts contracts.ReadOpts) (secretv0alpha1.KeeperConfig, error) {
return nil, nil
}
@@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
)
func ProvideSecureValueMetadataStorage(db db.DB, features featuremgmt.FeatureToggles, accessClient claims.AccessClient) (contracts.SecureValueMetadataStorage, error) {
@@ -28,15 +27,15 @@ type secureValueMetadataStorage struct {
accessClient claims.AccessClient
}
func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error) {
func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
return nil, nil
}
func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string) (*secretv0alpha1.SecureValue, error) {
func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) {
return nil, nil
}
func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue) (*secretv0alpha1.SecureValue, error) {
func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
return nil, nil
}
@@ -44,6 +43,18 @@ func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube
return nil
}
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace, options *internalversion.ListOptions) (*secretv0alpha1.SecureValueList, error) {
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (*secretv0alpha1.SecureValueList, error) {
return nil, nil
}
func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error {
return nil
}
func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error {
return nil
}
func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) {
return nil, nil
}