SecretsManager: Various utils for usage insights, outbox and secretkeeper (#106010)

* SecretsManager: utils for usage insights on ST mode

Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>

* SecretsManager: add assert

Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com>

* SecretsManager: Remove encryption scope option

Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>

* SecretsManager: add fake keeper

Co-authored-by: Dana Axinte <53751979+dana-axinte@users.noreply.github.com>
Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com>
Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>

---------

Co-authored-by: Matheus Macabu <macabu@users.noreply.github.com>
Co-authored-by: PoorlyDefinedBehaviour <brunotj2015@hotmail.com>
This commit is contained in:
Dana Axinte
2025-05-28 12:46:54 +01:00
committed by GitHub
co-authored by Matheus Macabu PoorlyDefinedBehaviour
parent 45c361e307
commit b4cd51810b
7 changed files with 169 additions and 23 deletions
+17
View File
@@ -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) {}
+26
View File
@@ -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)
}
@@ -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")
})
}
@@ -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
@@ -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
}
@@ -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)
}
@@ -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
}