diff --git a/pkg/infra/usagestats/noop.go b/pkg/infra/usagestats/noop.go new file mode 100644 index 00000000000..d5beec607a9 --- /dev/null +++ b/pkg/infra/usagestats/noop.go @@ -0,0 +1,17 @@ +package usagestats + +import "context" + +type NoopUsageStats struct{} + +var _ Service = &NoopUsageStats{} + +func (usm *NoopUsageStats) RegisterMetricsFunc(_ MetricsFunc) {} + +func (usm *NoopUsageStats) GetUsageReport(_ context.Context) (Report, error) { + return Report{}, nil +} + +func (usm *NoopUsageStats) RegisterSendReportCallback(_ SendReportCallbackFunc) {} + +func (usm *NoopUsageStats) SetReadyToReport(_ context.Context) {} diff --git a/pkg/registry/apis/secret/assert/assert.go b/pkg/registry/apis/secret/assert/assert.go new file mode 100644 index 00000000000..43f9779440e --- /dev/null +++ b/pkg/registry/apis/secret/assert/assert.go @@ -0,0 +1,26 @@ +package assert + +import ( + "errors" + "fmt" +) + +func True(expr bool, msg string, args ...any) { + if !expr { + if len(args) > 0 { + panic(fmt.Sprintf(msg, args...)) + } else { + panic(msg) + } + } +} + +func ErrorIs(err1, err2 error) { + if !errors.Is(err1, err2) { + panic(fmt.Sprintf("expected error %T(%+v) to be %T(%+v)", err1, err1, err2, err2)) + } +} + +func Equal[T comparable](v1 T, v2 T, msg string) { + True(v1 == v2, "expected %+v to equal %+v: %s", v1, v2, msg) +} diff --git a/pkg/registry/apis/secret/assert/assert_test.go b/pkg/registry/apis/secret/assert/assert_test.go new file mode 100644 index 00000000000..35e284ff89f --- /dev/null +++ b/pkg/registry/apis/secret/assert/assert_test.go @@ -0,0 +1,52 @@ +package assert + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTrue(t *testing.T) { + t.Parallel() + + require.PanicsWithValue(t, "error msg", func() { + True(1 == 2, "error msg") + }) + + require.PanicsWithValue(t, "error msg 1", func() { + True(1 == 2, "error msg %d", 1) + }) + + require.NotPanics(t, func() { + True(true, "oops") + }) +} + +func TestErrorIs(t *testing.T) { + t.Parallel() + + err := errors.New("some error") + + require.PanicsWithValue(t, "expected error *errors.errorString(other error) to be *errors.errorString(some error)", func() { + ErrorIs(fmt.Errorf("other error"), err) + }) + + require.NotPanics(t, func() { + ErrorIs(err, err) + ErrorIs(fmt.Errorf("something: %w", err), err) + }) +} + +func TestEqual(t *testing.T) { + t.Parallel() + + require.PanicsWithValue(t, "expected 1 to equal 2: details", func() { + Equal(1, 2, "details") + }) + + require.NotPanics(t, func() { + Equal("a", "a", "details") + }) +} diff --git a/pkg/registry/apis/secret/contracts/encryption.go b/pkg/registry/apis/secret/contracts/encryption.go index 164143ce423..915674179c0 100644 --- a/pkg/registry/apis/secret/contracts/encryption.go +++ b/pkg/registry/apis/secret/contracts/encryption.go @@ -8,31 +8,13 @@ type EncryptionManager interface { // For those specific use cases where the encryption operation cannot be moved outside // the database transaction, look at database-specific methods present at the specific // implementation present at manager.EncryptionService. - Encrypt(ctx context.Context, namespace string, payload []byte, opt EncryptionOptions) ([]byte, error) + Encrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) Decrypt(ctx context.Context, namespace string, payload []byte) ([]byte, error) RotateDataKeys(ctx context.Context, namespace string) error ReEncryptDataKeys(ctx context.Context, namespace string) error } -type EncryptionOptions func() string - -// EncryptWithoutScope uses a root level data key for encryption (DEK), -// in other words this DEK is not bound to any specific scope (not attached to any user, org, etc.). -func EncryptWithoutScope() EncryptionOptions { - return func() string { - return "root" - } -} - -// EncryptWithScope uses a data key for encryption bound to some specific scope (i.e., user, org, etc.). -// Scope should look like "user:10", "org:1". -func EncryptWithScope(scope string) EncryptionOptions { - return func() string { - return scope - } -} - type EncryptedValue struct { UID string Namespace string diff --git a/pkg/registry/apis/secret/secretkeeper/fakes/fake_keeper.go b/pkg/registry/apis/secret/secretkeeper/fakes/fake_keeper.go new file mode 100644 index 00000000000..0c8408a04bd --- /dev/null +++ b/pkg/registry/apis/secret/secretkeeper/fakes/fake_keeper.go @@ -0,0 +1,68 @@ +package fakes + +import ( + "context" + "errors" + + "github.com/google/uuid" + + secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/secret/contracts" +) + +var ErrSecretNotFound = errors.New("secret not found") + +type FakeKeeper struct { + values map[string]map[string]string +} + +var _ contracts.Keeper = (*FakeKeeper)(nil) + +func NewFakeKeeper() *FakeKeeper { + return &FakeKeeper{ + values: make(map[string]map[string]string), + } +} + +func (s *FakeKeeper) Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) { + ns, ok := s.values[namespace] + if !ok { + ns = make(map[string]string) + } + uid := uuid.New().String() + ns[uid] = exposedValueOrRef + s.values[namespace] = ns + + return contracts.ExternalID(uid), nil +} + +func (s *FakeKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) (secretv0alpha1.ExposedSecureValue, error) { + ns, ok := s.values[namespace] + if !ok { + return "", ErrSecretNotFound + } + exposedVal, ok := ns[externalID.String()] + if !ok { + return "", ErrSecretNotFound + } + + return secretv0alpha1.NewExposedSecureValue(exposedVal), nil +} + +func (s *FakeKeeper) Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID) error { + return nil +} + +func (s *FakeKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, externalID contracts.ExternalID, exposedValueOrRef string) error { + ns, ok := s.values[namespace] + if !ok { + return ErrSecretNotFound + } + _, ok = ns[externalID.String()] + if !ok { + return ErrSecretNotFound + } + + ns[externalID.String()] = exposedValueOrRef + return nil +} diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go index dce87821d2a..700abab63a5 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper.go @@ -29,11 +29,12 @@ func NewSQLKeeper( } } -func (s *SQLKeeper) Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) { +// TODO: parameter cfg is not being used +func (s *SQLKeeper) Store(ctx context.Context, _ secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) { ctx, span := s.tracer.Start(ctx, "sqlKeeper.Store") defer span.End() - encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef), contracts.EncryptWithoutScope()) + encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) if err != nil { return "", fmt.Errorf("unable to encrypt value: %w", err) } @@ -79,7 +80,7 @@ func (s *SQLKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig, ctx, span := s.tracer.Start(ctx, "sqlKeeper.Update") defer span.End() - encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef), contracts.EncryptWithoutScope()) + encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) if err != nil { return fmt.Errorf("unable to encrypt value: %w", err) } diff --git a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go index 413a5fa7530..b8a78e92dab 100644 --- a/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go +++ b/pkg/registry/apis/secret/secretkeeper/sqlkeeper/keeper_test.go @@ -151,7 +151,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) { // While we don't have the real implementation, use an in-memory one type inMemoryEncryptionManager struct{} -func (m *inMemoryEncryptionManager) Encrypt(_ context.Context, _ string, value []byte, _ contracts.EncryptionOptions) ([]byte, error) { +func (m *inMemoryEncryptionManager) Encrypt(_ context.Context, _ string, value []byte) ([]byte, error) { return []byte(base64.StdEncoding.EncodeToString(value)), nil }