Secrets: changes to allow a 3rd party keeper / secret references (#115156)
* Secrets: changes to allow a 3rd party keeper / secret references * fix test * make gofmt * lint * fix tests * assign aws secrets manager to @grafana/grafana-operator-experience-squad * rename Keeper.Reference to Keeper.RetrieveReference * rename ModelSecretsManager to ModelAWSSecretsManager * validator: ensure that only one of keeper.Spec.Aws.AccessKey or keeper.Spec.Aws.AssumeRole are set * move secrets manager dep / go mod tidy * move secrets manager dep * keeper validator: move 3rd party secret stores validation to their own functions * add github.com/aws/aws-sdk-go-v2/service/secretsmanager pkg/extensions/enterprise_imports * make update-workspace * undo go.mod changes in /apps * make update-workspace * fix test * add github.com/aws/aws-sdk-go-v2/service/secretsmanager to enterprise_imports * make update-workspace * gcworker: handle refs * make update-workspace * create toggle: FeatureStageExperimental * allow features.IsEnabled for now * format
This commit is contained in:
@@ -2,6 +2,7 @@ package testutils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -143,7 +144,8 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
realMigrationExecutor, err := encryptionstorage.ProvideEncryptedValueMigrationExecutor(database, tracer, encryptedValueStorage, globalEncryptedValueStorage)
|
||||
require.NoError(t, err)
|
||||
|
||||
var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper)
|
||||
mockAwsKeeper := NewModelSecretsManager()
|
||||
var keeperService contracts.KeeperService = newKeeperServiceWrapper(sqlKeeper, mockAwsKeeper)
|
||||
|
||||
if setupCfg.KeeperService != nil {
|
||||
keeperService = setupCfg.KeeperService
|
||||
@@ -190,6 +192,7 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
|
||||
Clock: clock,
|
||||
KeeperService: keeperService,
|
||||
KeeperMetadataStorage: keeperMetadataStorage,
|
||||
ModelSecretsManager: mockAwsKeeper,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +215,8 @@ type Sut struct {
|
||||
Clock *FakeClock
|
||||
KeeperService contracts.KeeperService
|
||||
KeeperMetadataStorage contracts.KeeperMetadataStorage
|
||||
// A mock of AWS secrets manager that implements contracts.Keeper
|
||||
ModelSecretsManager *ModelAWSSecretsManager
|
||||
}
|
||||
|
||||
type CreateSvConfig struct {
|
||||
@@ -260,16 +265,54 @@ func (s *Sut) DeleteSv(ctx context.Context, namespace, name string) (*secretv1be
|
||||
return sv, err
|
||||
}
|
||||
|
||||
type keeperServiceWrapper struct {
|
||||
keeper contracts.Keeper
|
||||
type CreateKeeperConfig struct {
|
||||
// The default keeper payload. Mutate it to change which keeper ends up being created
|
||||
Keeper *secretv1beta1.Keeper
|
||||
}
|
||||
|
||||
func newKeeperServiceWrapper(keeper contracts.Keeper) *keeperServiceWrapper {
|
||||
return &keeperServiceWrapper{keeper: keeper}
|
||||
func (s *Sut) CreateAWSKeeper(ctx context.Context) (*secretv1beta1.Keeper, error) {
|
||||
return s.CreateKeeper(ctx, func(cfg *CreateKeeperConfig) {
|
||||
cfg.Keeper.Spec = secretv1beta1.KeeperSpec{
|
||||
Aws: &secretv1beta1.KeeperAWSConfig{},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Sut) CreateKeeper(ctx context.Context, opts ...func(*CreateKeeperConfig)) (*secretv1beta1.Keeper, error) {
|
||||
cfg := CreateKeeperConfig{
|
||||
Keeper: &secretv1beta1.Keeper{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "sv1",
|
||||
Namespace: "ns1",
|
||||
},
|
||||
Spec: secretv1beta1.KeeperSpec{
|
||||
Aws: &secretv1beta1.KeeperAWSConfig{},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&cfg)
|
||||
}
|
||||
|
||||
return s.KeeperMetadataStorage.Create(ctx, cfg.Keeper, "actor-uid")
|
||||
}
|
||||
|
||||
type keeperServiceWrapper struct {
|
||||
sqlKeeper *sqlkeeper.SQLKeeper
|
||||
awsKeeper *ModelAWSSecretsManager
|
||||
}
|
||||
|
||||
func newKeeperServiceWrapper(sqlKeeper *sqlkeeper.SQLKeeper, awsKeeper *ModelAWSSecretsManager) *keeperServiceWrapper {
|
||||
return &keeperServiceWrapper{sqlKeeper: sqlKeeper, awsKeeper: awsKeeper}
|
||||
}
|
||||
|
||||
func (wrapper *keeperServiceWrapper) KeeperForConfig(cfg secretv1beta1.KeeperConfig) (contracts.Keeper, error) {
|
||||
return wrapper.keeper, nil
|
||||
switch cfg.(type) {
|
||||
case *secretv1beta1.NamedKeeperConfig[*secretv1beta1.KeeperAWSConfig]:
|
||||
return wrapper.awsKeeper, nil
|
||||
default:
|
||||
return wrapper.sqlKeeper, nil
|
||||
}
|
||||
}
|
||||
|
||||
func CreateUserAuthContext(ctx context.Context, namespace string, permissions map[string][]string) context.Context {
|
||||
@@ -390,3 +433,113 @@ type NoopMigrationExecutor struct {
|
||||
func (e *NoopMigrationExecutor) Execute(ctx context.Context) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// A mock of AWS secrets manager, used for testing.
|
||||
type ModelAWSSecretsManager struct {
|
||||
secrets map[string]entry
|
||||
alreadyDeleted map[string]bool
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
exposedValueOrRef string
|
||||
externalID string
|
||||
}
|
||||
|
||||
func NewModelSecretsManager() *ModelAWSSecretsManager {
|
||||
return &ModelAWSSecretsManager{
|
||||
secrets: make(map[string]entry),
|
||||
alreadyDeleted: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) Store(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64, exposedValueOrRef string) (externalID contracts.ExternalID, err error) {
|
||||
if exposedValueOrRef == "" {
|
||||
return "", fmt.Errorf("failed to satisfy constraint: Member must have length greater than or equal to 1")
|
||||
}
|
||||
|
||||
versionID := buildVersionID(namespace, name, version)
|
||||
if e, ok := m.secrets[versionID]; ok {
|
||||
// Ignore duplicated requests
|
||||
if e.exposedValueOrRef == exposedValueOrRef {
|
||||
return contracts.ExternalID(e.externalID), nil
|
||||
}
|
||||
|
||||
// Tried to create a secret that already exists
|
||||
return "", fmt.Errorf("ResourceExistsException: The operation failed because the secret %+v already exists", versionID)
|
||||
}
|
||||
|
||||
// First time creating the secret
|
||||
entry := entry{
|
||||
exposedValueOrRef: exposedValueOrRef,
|
||||
externalID: "external-id",
|
||||
}
|
||||
m.secrets[versionID] = entry
|
||||
|
||||
return contracts.ExternalID(entry.externalID), nil
|
||||
}
|
||||
|
||||
// Used to simulate the creation of secrets in the 3rd party secret store
|
||||
func (m *ModelAWSSecretsManager) Create(name, value string) {
|
||||
m.secrets[name] = entry{
|
||||
exposedValueOrRef: value,
|
||||
externalID: fmt.Sprintf("external_id_%+v", value),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) Expose(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) (exposedValue secretv1beta1.ExposedSecureValue, err error) {
|
||||
versionID := buildVersionID(namespace, name, version)
|
||||
|
||||
if m.deleted(versionID) {
|
||||
return "", fmt.Errorf("InvalidRequestException: You can't perform this operation on the secret because it was marked for deletion")
|
||||
}
|
||||
|
||||
entry, ok := m.secrets[versionID]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("ResourceNotFoundException: Secrets Manager can't find the specified secret")
|
||||
}
|
||||
|
||||
return secretv1beta1.ExposedSecureValue(entry.exposedValueOrRef), nil
|
||||
}
|
||||
|
||||
// TODO: this could be namespaced to make it more realistic
|
||||
func (m *ModelAWSSecretsManager) RetrieveReference(ctx context.Context, _ secretv1beta1.KeeperConfig, ref string) (secretv1beta1.ExposedSecureValue, error) {
|
||||
entry, ok := m.secrets[ref]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("ResourceNotFoundException: Secrets Manager can't find the specified secret")
|
||||
}
|
||||
return secretv1beta1.ExposedSecureValue(entry.exposedValueOrRef), nil
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) Delete(ctx context.Context, cfg secretv1beta1.KeeperConfig, namespace xkube.Namespace, name string, version int64) (err error) {
|
||||
versionID := buildVersionID(namespace, name, version)
|
||||
|
||||
// Deleting a secret that existed at some point is idempotent
|
||||
if m.deleted(versionID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the secret is being deleted for the first time
|
||||
if m.exists(versionID) {
|
||||
m.delete(versionID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) deleted(versionID string) bool {
|
||||
return m.alreadyDeleted[versionID]
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) exists(versionID string) bool {
|
||||
_, ok := m.secrets[versionID]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *ModelAWSSecretsManager) delete(versionID string) {
|
||||
m.alreadyDeleted[versionID] = true
|
||||
delete(m.secrets, versionID)
|
||||
}
|
||||
|
||||
func buildVersionID(namespace xkube.Namespace, name string, version int64) string {
|
||||
return fmt.Sprintf("%s/%s/%d", namespace, name, version)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user