SecretsManager: Introduce metrics and logs (#107582)

Co-authored-by: Michael Mandrus <michael.mandrus@grafana.com>
This commit is contained in:
Dana Axinte
2025-07-03 17:32:18 +01:00
committed by GitHub
co-authored by Michael Mandrus
parent 66d9a33cc9
commit a59ec345c2
19 changed files with 685 additions and 35 deletions
@@ -77,7 +77,7 @@ func TestEncryptionService_DataKeys(t *testing.T) {
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
tracer := noop.NewTracerProvider().Tracer("test") tracer := noop.NewTracerProvider().Tracer("test")
store, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features) store, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
ctx := context.Background() ctx := context.Background()
@@ -183,7 +183,7 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
tracer := noop.NewTracerProvider().Tracer("test") tracer := noop.NewTracerProvider().Tracer("test")
encryptionStore, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features) encryptionStore, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
encMgr, err := ProvideEncryptionManager( encMgr, err := ProvideEncryptionManager(
@@ -374,7 +374,7 @@ func TestIntegration_SecretsService(t *testing.T) {
EncryptionProvider: "secretKey.v1", EncryptionProvider: "secretKey.v1",
}, },
} }
store, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features) store, err := encryptionstorage.ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
usageStats := &usagestats.UsageStatsMock{T: t} usageStats := &usagestats.UsageStatsMock{T: t}
@@ -31,7 +31,7 @@ func setupTestService(tb testing.TB) *EncryptionManager {
EncryptionProvider: "secretKey.v1", EncryptionProvider: "secretKey.v1",
}, },
} }
store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features) store, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
require.NoError(tb, err) require.NoError(tb, err)
usageStats := &usagestats.UsageStatsMock{T: tb} usageStats := &usagestats.UsageStatsMock{T: tb}
@@ -0,0 +1,71 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
namespace = "grafana_secrets_manager"
subsystem = "keeper"
)
// KeeperMetrics is a struct that contains all the metrics for an implementation of all keepers.
type KeeperMetrics struct {
StoreDuration *prometheus.HistogramVec
UpdateDuration *prometheus.HistogramVec
ExposeDuration *prometheus.HistogramVec
DeleteDuration *prometheus.HistogramVec
}
func newKeeperMetrics() *KeeperMetrics {
return &KeeperMetrics{
StoreDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "store_duration_seconds",
Help: "Duration of keeper store operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
UpdateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "update_duration_seconds",
Help: "Duration of keeper update operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
ExposeDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "expose_duration_seconds",
Help: "Duration of keeper expose operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
DeleteDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "delete_duration_seconds",
Help: "Duration of keeper delete operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
}
}
// NewKeeperMetrics creates a new KeeperMetrics struct containing registered metrics
func NewKeeperMetrics(reg prometheus.Registerer) *KeeperMetrics {
m := newKeeperMetrics()
if reg != nil {
reg.MustRegister(
m.StoreDuration,
m.UpdateDuration,
m.ExposeDuration,
m.DeleteDuration,
)
}
return m
}
func NewTestMetrics() *KeeperMetrics {
return newKeeperMetrics()
}
@@ -6,6 +6,7 @@ import (
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper" "github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/sqlkeeper"
"github.com/prometheus/client_golang/prometheus"
) )
// OSSKeeperService is the OSS implementation of the Service interface. // OSSKeeperService is the OSS implementation of the Service interface.
@@ -19,10 +20,11 @@ func ProvideService(
tracer trace.Tracer, tracer trace.Tracer,
store contracts.EncryptedValueStorage, store contracts.EncryptedValueStorage,
encryptionManager contracts.EncryptionManager, encryptionManager contracts.EncryptionManager,
reg prometheus.Registerer,
) (*OSSKeeperService, error) { ) (*OSSKeeperService, error) {
return &OSSKeeperService{ return &OSSKeeperService{
// TODO: rename to system keeper or something like that // TODO: rename to system keeper or something like that
systemKeeper: sqlkeeper.NewSQLKeeper(tracer, encryptionManager, store), systemKeeper: sqlkeeper.NewSQLKeeper(tracer, encryptionManager, store, reg),
}, nil }, nil
} }
@@ -49,7 +49,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error)
database := database.ProvideDatabase(testDB, tracer) database := database.ProvideDatabase(testDB, tracer)
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features) dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features) encValueStore, err := encryptionstorage.ProvideEncryptedValueStorage(database, tracer, features)
@@ -59,7 +59,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error)
require.NoError(t, err) require.NoError(t, err)
// Initialize the keeper service // Initialize the keeper service
keeperService, err := ProvideService(tracer, encValueStore, encryptionManager) keeperService, err := ProvideService(tracer, encValueStore, encryptionManager, nil)
return keeperService, err return keeperService, err
} }
@@ -3,9 +3,12 @@ package sqlkeeper
import ( import (
"context" "context"
"fmt" "fmt"
"time"
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper/metrics"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
@@ -14,6 +17,7 @@ type SQLKeeper struct {
tracer trace.Tracer tracer trace.Tracer
encryptionManager contracts.EncryptionManager encryptionManager contracts.EncryptionManager
store contracts.EncryptedValueStorage store contracts.EncryptedValueStorage
metrics *metrics.KeeperMetrics
} }
var _ contracts.Keeper = (*SQLKeeper)(nil) var _ contracts.Keeper = (*SQLKeeper)(nil)
@@ -22,19 +26,21 @@ func NewSQLKeeper(
tracer trace.Tracer, tracer trace.Tracer,
encryptionManager contracts.EncryptionManager, encryptionManager contracts.EncryptionManager,
store contracts.EncryptedValueStorage, store contracts.EncryptedValueStorage,
reg prometheus.Registerer,
) *SQLKeeper { ) *SQLKeeper {
return &SQLKeeper{ return &SQLKeeper{
tracer: tracer, tracer: tracer,
encryptionManager: encryptionManager, encryptionManager: encryptionManager,
store: store, store: store,
metrics: metrics.NewKeeperMetrics(reg),
} }
} }
// TODO: parameter cfg is not being used func (s *SQLKeeper) Store(ctx context.Context, cfg secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) {
func (s *SQLKeeper) Store(ctx context.Context, _ secretv0alpha1.KeeperConfig, namespace string, exposedValueOrRef string) (contracts.ExternalID, error) {
ctx, span := s.tracer.Start(ctx, "SQLKeeper.Store", trace.WithAttributes(attribute.String("namespace", namespace))) ctx, span := s.tracer.Start(ctx, "SQLKeeper.Store", trace.WithAttributes(attribute.String("namespace", namespace)))
defer span.End() defer span.End()
start := time.Now()
encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef))
if err != nil { if err != nil {
return "", fmt.Errorf("unable to encrypt value: %w", err) return "", fmt.Errorf("unable to encrypt value: %w", err)
@@ -45,8 +51,8 @@ func (s *SQLKeeper) Store(ctx context.Context, _ secretv0alpha1.KeeperConfig, na
return "", fmt.Errorf("unable to store encrypted value: %w", err) return "", fmt.Errorf("unable to store encrypted value: %w", err)
} }
s.metrics.StoreDuration.WithLabelValues(string(cfg.Type())).Observe(time.Since(start).Seconds())
externalID := contracts.ExternalID(encryptedVal.UID) externalID := contracts.ExternalID(encryptedVal.UID)
span.SetAttributes(attribute.String("externalID", externalID.String())) span.SetAttributes(attribute.String("externalID", externalID.String()))
return externalID, nil return externalID, nil
@@ -59,6 +65,7 @@ func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig,
)) ))
defer span.End() defer span.End()
start := time.Now()
encryptedValue, err := s.store.Get(ctx, namespace, externalID.String()) encryptedValue, err := s.store.Get(ctx, namespace, externalID.String())
if err != nil { if err != nil {
return "", fmt.Errorf("unable to get encrypted value: %w", err) return "", fmt.Errorf("unable to get encrypted value: %w", err)
@@ -70,6 +77,8 @@ func (s *SQLKeeper) Expose(ctx context.Context, cfg secretv0alpha1.KeeperConfig,
} }
exposedValue := secretv0alpha1.NewExposedSecureValue(string(exposedBytes)) exposedValue := secretv0alpha1.NewExposedSecureValue(string(exposedBytes))
s.metrics.ExposeDuration.WithLabelValues(string(cfg.Type())).Observe(time.Since(start).Seconds())
return exposedValue, nil return exposedValue, nil
} }
@@ -80,10 +89,14 @@ func (s *SQLKeeper) Delete(ctx context.Context, cfg secretv0alpha1.KeeperConfig,
)) ))
defer span.End() defer span.End()
start := time.Now()
err := s.store.Delete(ctx, namespace, externalID.String()) err := s.store.Delete(ctx, namespace, externalID.String())
if err != nil { if err != nil {
return fmt.Errorf("failed to delete encrypted value: %w", err) return fmt.Errorf("failed to delete encrypted value: %w", err)
} }
s.metrics.DeleteDuration.WithLabelValues(string(cfg.Type())).Observe(time.Since(start).Seconds())
return nil return nil
} }
@@ -94,6 +107,7 @@ func (s *SQLKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig,
)) ))
defer span.End() defer span.End()
start := time.Now()
encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef)) encryptedData, err := s.encryptionManager.Encrypt(ctx, namespace, []byte(exposedValueOrRef))
if err != nil { if err != nil {
return fmt.Errorf("unable to encrypt value: %w", err) return fmt.Errorf("unable to encrypt value: %w", err)
@@ -103,5 +117,8 @@ func (s *SQLKeeper) Update(ctx context.Context, cfg secretv0alpha1.KeeperConfig,
if err != nil { if err != nil {
return fmt.Errorf("failed to update encrypted value: %w", err) return fmt.Errorf("failed to update encrypted value: %w", err)
} }
s.metrics.UpdateDuration.WithLabelValues(string(cfg.Type())).Observe(time.Since(start).Seconds())
return nil return nil
} }
@@ -159,7 +159,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) {
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
// Initialize the encryption manager // Initialize the encryption manager
dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features) dataKeyStore, err := encryptionstorage.ProvideDataKeyStorage(database, tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
usageStats := &usagestats.UsageStatsMock{T: t} usageStats := &usagestats.UsageStatsMock{T: t}
@@ -178,7 +178,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*SQLKeeper, error) {
require.NoError(t, err) require.NoError(t, err)
// Initialize the SQLKeeper // Initialize the SQLKeeper
sqlKeeper := NewSQLKeeper(tracer, encMgr, encValueStore) sqlKeeper := NewSQLKeeper(tracer, encMgr, encValueStore, nil)
return sqlKeeper, nil return sqlKeeper, nil
} }
-7
View File
@@ -2,17 +2,10 @@ package setting
import ( import (
"regexp" "regexp"
"time"
"github.com/grafana/grafana/pkg/services/kmsproviders" "github.com/grafana/grafana/pkg/services/kmsproviders"
) )
type EncryptionSettings struct {
DataKeysCacheTTL time.Duration
DataKeysCleanupInterval time.Duration
Algorithm string
}
type SecretsManagerSettings struct { type SecretsManagerSettings struct {
SecretKey string SecretKey string
EncryptionProvider string EncryptionProvider string
@@ -9,6 +9,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
@@ -19,9 +20,15 @@ type encryptionStoreImpl struct {
dialect sqltemplate.Dialect dialect sqltemplate.Dialect
tracer trace.Tracer tracer trace.Tracer
log log.Logger log log.Logger
metrics *DataKeyMetrics
} }
func ProvideDataKeyStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.DataKeyStorage, error) { func ProvideDataKeyStorage(
db contracts.Database,
tracer trace.Tracer,
features featuremgmt.FeatureToggles,
registerer prometheus.Registerer,
) (contracts.DataKeyStorage, error) {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) ||
!features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) {
return &encryptionStoreImpl{}, nil return &encryptionStoreImpl{}, nil
@@ -32,6 +39,7 @@ func ProvideDataKeyStorage(db contracts.Database, tracer trace.Tracer, features
dialect: sqltemplate.DialectForDriver(db.DriverName()), dialect: sqltemplate.DialectForDriver(db.DriverName()),
tracer: tracer, tracer: tracer,
log: log.New("encryption.store"), log: log.New("encryption.store"),
metrics: NewDataKeyMetrics(registerer),
} }
return store, nil return store, nil
@@ -31,7 +31,7 @@ func TestEncryptionStoreImpl_DataKeyLifecycle(t *testing.T) {
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
tracer := noop.NewTracerProvider().Tracer("test") tracer := noop.NewTracerProvider().Tracer("test")
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
store, err := ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features) store, err := ProvideDataKeyStorage(database.ProvideDatabase(testDB, tracer), tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
ctx := context.Background() ctx := context.Background()
+98
View File
@@ -0,0 +1,98 @@
package encryption
import (
"github.com/prometheus/client_golang/prometheus"
)
const (
namespace = "grafana_secrets_manager"
subsystem = "data_key_storage"
)
// DataKeyMetrics is a struct that contains all the metrics for all operations of encryption storage.
type DataKeyMetrics struct {
CreateDataKeyDuration prometheus.Histogram
GetDataKeyDuration prometheus.Histogram
GetCurrentDataKeyDuration prometheus.Histogram
GetAllDataKeysDuration prometheus.Histogram
DisableDataKeysDuration prometheus.Histogram
DeleteDataKeyDuration prometheus.Histogram
ReEncryptDataKeysDuration prometheus.Histogram
}
func newDataKeyMetrics() *DataKeyMetrics {
return &DataKeyMetrics{
CreateDataKeyDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "create_data_key_duration_seconds",
Help: "Duration of create data key operations",
Buckets: prometheus.DefBuckets,
}),
GetDataKeyDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "get_data_key_duration_seconds",
Help: "Duration of get data key operations",
Buckets: prometheus.DefBuckets,
}),
GetCurrentDataKeyDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "get_current_data_key_duration_seconds",
Help: "Duration of get current data key operations",
Buckets: prometheus.DefBuckets,
}),
GetAllDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "get_all_data_keys_duration_seconds",
Help: "Duration of get all data keys operations",
Buckets: prometheus.DefBuckets,
}),
DisableDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "disable_data_keys_duration_seconds",
Help: "Duration of disable data keys operations",
Buckets: prometheus.DefBuckets,
}),
DeleteDataKeyDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "delete_data_key_duration_seconds",
Help: "Duration of delete data key operations",
Buckets: prometheus.DefBuckets,
}),
ReEncryptDataKeysDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "re_encrypt_data_keys_duration_seconds",
Help: "Duration of re-encrypt data keys operations",
Buckets: prometheus.DefBuckets,
}),
}
}
// NewDataKeyMetrics returns a singleton instance of the SecretsMetrics struct containing registered metrics
func NewDataKeyMetrics(reg prometheus.Registerer) *DataKeyMetrics {
m := newDataKeyMetrics()
if reg != nil {
reg.MustRegister(
m.CreateDataKeyDuration,
m.GetDataKeyDuration,
m.GetCurrentDataKeyDuration,
m.GetAllDataKeysDuration,
m.DisableDataKeysDuration,
m.DeleteDataKeyDuration,
m.ReEncryptDataKeysDuration,
)
}
return m
}
func NewTestMetrics() *DataKeyMetrics {
return newDataKeyMetrics()
}
+45 -3
View File
@@ -3,12 +3,15 @@ package metadata
import ( import (
"context" "context"
"fmt" "fmt"
"time"
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/storage/secret/metadata/metrics"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@@ -19,11 +22,17 @@ type keeperMetadataStorage struct {
db contracts.Database db contracts.Database
dialect sqltemplate.Dialect dialect sqltemplate.Dialect
tracer trace.Tracer tracer trace.Tracer
metrics *metrics.StorageMetrics
} }
var _ contracts.KeeperMetadataStorage = (*keeperMetadataStorage)(nil) var _ contracts.KeeperMetadataStorage = (*keeperMetadataStorage)(nil)
func ProvideKeeperMetadataStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.KeeperMetadataStorage, error) { func ProvideKeeperMetadataStorage(
db contracts.Database,
tracer trace.Tracer,
features featuremgmt.FeatureToggles,
reg prometheus.Registerer,
) (contracts.KeeperMetadataStorage, error) {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) ||
!features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) {
return &keeperMetadataStorage{}, nil return &keeperMetadataStorage{}, nil
@@ -33,10 +42,12 @@ func ProvideKeeperMetadataStorage(db contracts.Database, tracer trace.Tracer, fe
db: db, db: db,
dialect: sqltemplate.DialectForDriver(db.DriverName()), dialect: sqltemplate.DialectForDriver(db.DriverName()),
tracer: tracer, tracer: tracer,
metrics: metrics.NewStorageMetrics(reg),
}, nil }, nil
} }
func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) { func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Create", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Create", trace.WithAttributes(
attribute.String("name", keeper.GetName()), attribute.String("name", keeper.GetName()),
attribute.String("namespace", keeper.GetNamespace()), attribute.String("namespace", keeper.GetNamespace()),
@@ -53,7 +64,6 @@ func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alph
SQLTemplate: sqltemplate.New(s.dialect), SQLTemplate: sqltemplate.New(s.dialect),
Row: row, Row: row,
} }
query, err := sqltemplate.Execute(sqlKeeperCreate, req) query, err := sqltemplate.Execute(sqlKeeperCreate, req)
if err != nil { if err != nil {
return nil, fmt.Errorf("execute template %q: %w", sqlKeeperCreate.Name(), err) return nil, fmt.Errorf("execute template %q: %w", sqlKeeperCreate.Name(), err)
@@ -65,6 +75,11 @@ func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alph
return err return err
} }
// Validate before inserting that any `secureValues` referenced exist and do not reference other third-party keepers.
if err := s.validateSecureValueReferences(ctx, keeper); err != nil {
return err
}
result, err := s.db.ExecContext(ctx, query, req.GetArgs()...) result, err := s.db.ExecContext(ctx, query, req.GetArgs()...)
if err != nil { if err != nil {
return fmt.Errorf("inserting row: %w", err) return fmt.Errorf("inserting row: %w", err)
@@ -90,10 +105,14 @@ func (s *keeperMetadataStorage) Create(ctx context.Context, keeper *secretv0alph
return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err) return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err)
} }
s.metrics.KeeperMetadataCreateDuration.WithLabelValues(string(createdKeeper.Spec.GetType())).Observe(time.Since(start).Seconds())
s.metrics.KeeperMetadataCreateCount.WithLabelValues(string(createdKeeper.Spec.GetType())).Inc()
return createdKeeper, nil return createdKeeper, nil
} }
func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.Keeper, error) { func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.Keeper, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Read", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Read", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -111,6 +130,9 @@ func (s *keeperMetadataStorage) Read(ctx context.Context, namespace xkube.Namesp
return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err) return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err)
} }
s.metrics.KeeperMetadataGetDuration.WithLabelValues(string(keeper.Spec.GetType())).Observe(time.Since(start).Seconds())
s.metrics.KeeperMetadataGetCount.WithLabelValues(string(keeper.Spec.GetType())).Inc()
return keeper, nil return keeper, nil
} }
@@ -153,6 +175,7 @@ func (s *keeperMetadataStorage) read(ctx context.Context, namespace, name string
} }
func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) { func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0alpha1.Keeper, actorUID string) (*secretv0alpha1.Keeper, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Update", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Update", trace.WithAttributes(
attribute.String("name", newKeeper.GetName()), attribute.String("name", newKeeper.GetName()),
attribute.String("namespace", newKeeper.GetNamespace()), attribute.String("namespace", newKeeper.GetNamespace()),
@@ -168,6 +191,11 @@ func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0a
return err return err
} }
// Validate before updating that any `secureValues` referenced exists and does not reference other third-party keepers.
if err := s.validateSecureValueReferences(ctx, newKeeper); err != nil {
return err
}
// Read old value first. // Read old value first.
oldKeeperRow, err := s.read(ctx, newKeeper.Namespace, newKeeper.Name, contracts.ReadOpts{ForUpdate: true}) oldKeeperRow, err := s.read(ctx, newKeeper.Namespace, newKeeper.Name, contracts.ReadOpts{ForUpdate: true})
if err != nil { if err != nil {
@@ -217,10 +245,14 @@ func (s *keeperMetadataStorage) Update(ctx context.Context, newKeeper *secretv0a
return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err) return nil, fmt.Errorf("failed to convert to kubernetes object: %w", err)
} }
s.metrics.KeeperMetadataUpdateDuration.WithLabelValues(string(keeper.Spec.GetType())).Observe(time.Since(start).Seconds())
s.metrics.KeeperMetadataUpdateCount.WithLabelValues(string(keeper.Spec.GetType())).Inc()
return keeper, nil return keeper, nil
} }
func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error { func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Delete", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.Delete", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -253,10 +285,14 @@ func (s *keeperMetadataStorage) Delete(ctx context.Context, namespace xkube.Name
return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, name, namespace) return fmt.Errorf("expected 1 row affected, got %d for %s on %s", rowsAffected, name, namespace)
} }
s.metrics.KeeperMetadataDeleteDuration.Observe(time.Since(start).Seconds())
s.metrics.KeeperMetadataDeleteCount.Inc()
return nil return nil
} }
func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (keeperList []secretv0alpha1.Keeper, err error) { func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (keeperList []secretv0alpha1.Keeper, err error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.List", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "KeeperMetadataStorage.List", trace.WithAttributes(
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
)) ))
@@ -306,6 +342,9 @@ func (s *keeperMetadataStorage) List(ctx context.Context, namespace xkube.Namesp
return nil, fmt.Errorf("read rows error: %w", err) return nil, fmt.Errorf("read rows error: %w", err)
} }
s.metrics.KeeperMetadataListDuration.Observe(time.Since(start).Seconds())
s.metrics.KeeperMetadataListCount.Inc()
return keepers, nil return keepers, nil
} }
@@ -467,9 +506,10 @@ func (s *keeperMetadataStorage) GetKeeperConfig(ctx context.Context, namespace s
// Check if keeper is the systemwide one. // Check if keeper is the systemwide one.
if name == nil { if name == nil {
return nil, nil return &secretv0alpha1.SystemKeeperConfig{}, nil
} }
start := time.Now()
span.SetAttributes(attribute.String("name", *name)) span.SetAttributes(attribute.String("name", *name))
// Load keeper config from metadata store, or TODO: keeper cache. // Load keeper config from metadata store, or TODO: keeper cache.
@@ -480,6 +520,8 @@ func (s *keeperMetadataStorage) GetKeeperConfig(ctx context.Context, namespace s
keeperConfig := toProvider(secretv0alpha1.KeeperType(kp.Type), kp.Payload) keeperConfig := toProvider(secretv0alpha1.KeeperType(kp.Type), kp.Payload)
s.metrics.KeeperMetadataGetKeeperConfigDuration.Observe(time.Since(start).Seconds())
// TODO: this would be a good place to check if credentials are secure values and load them. // TODO: this would be a good place to check if credentials are secure values and load them.
return keeperConfig, nil return keeperConfig, nil
} }
@@ -40,7 +40,7 @@ func Test_KeeperMetadataStorage_GetKeeperConfig(t *testing.T) {
// get system keeper config // get system keeper config
keeperConfig, err := keeperMetadataStorage.GetKeeperConfig(ctx, defaultKeeperNS, nil, contracts.ReadOpts{}) keeperConfig, err := keeperMetadataStorage.GetKeeperConfig(ctx, defaultKeeperNS, nil, contracts.ReadOpts{})
require.NoError(t, err) require.NoError(t, err)
require.Nil(t, keeperConfig) require.IsType(t, &secretv0alpha1.SystemKeeperConfig{}, keeperConfig)
}) })
t.Run("get test keeper config", func(t *testing.T) { t.Run("get test keeper config", func(t *testing.T) {
@@ -340,7 +340,7 @@ func initStorage(t *testing.T) contracts.KeeperMetadataStorage {
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
// Initialize the keeper storage // Initialize the keeper storage
keeperMetadataStorage, err := ProvideKeeperMetadataStorage(db, tracer, features) keeperMetadataStorage, err := ProvideKeeperMetadataStorage(db, tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
return keeperMetadataStorage return keeperMetadataStorage
} }
@@ -0,0 +1,356 @@
package metrics
import (
"sync"
"github.com/prometheus/client_golang/prometheus"
)
const (
namespace = "grafana_secrets_manager"
subsystem = "storage"
)
// StorageMetrics is a struct that contains all the metrics for all operations of secrets storage.
type StorageMetrics struct {
OutboxAppendDuration *prometheus.HistogramVec
OutboxReceiveDuration prometheus.Histogram
OutboxAppendCount *prometheus.CounterVec
OutboxReceiveCount prometheus.Counter
OutboxDeleteDuration prometheus.Histogram
OutboxDeleteCount prometheus.Counter
OutboxIncrementReceiveCountDuration prometheus.Histogram
OutboxIncrementReceiveCountCount prometheus.Counter
OutboxTotalMessageLifetimeDuration *prometheus.HistogramVec
KeeperMetadataCreateDuration *prometheus.HistogramVec
KeeperMetadataCreateCount *prometheus.CounterVec
KeeperMetadataUpdateDuration *prometheus.HistogramVec
KeeperMetadataUpdateCount *prometheus.CounterVec
KeeperMetadataDeleteDuration prometheus.Histogram
KeeperMetadataDeleteCount prometheus.Counter
KeeperMetadataGetDuration *prometheus.HistogramVec
KeeperMetadataGetCount *prometheus.CounterVec
KeeperMetadataListDuration prometheus.Histogram
KeeperMetadataListCount prometheus.Counter
KeeperMetadataGetKeeperConfigDuration prometheus.Histogram
SecureValueMetadataCreateDuration prometheus.Histogram
SecureValueMetadataCreateCount prometheus.Counter
SecureValueMetadataUpdateDuration prometheus.Histogram
SecureValueMetadataUpdateCount prometheus.Counter
SecureValueMetadataDeleteDuration prometheus.Histogram
SecureValueMetadataDeleteCount prometheus.Counter
SecureValueMetadataGetDuration prometheus.Histogram
SecureValueMetadataGetCount prometheus.Counter
SecureValueMetadataListDuration prometheus.Histogram
SecureValueMetadataListCount prometheus.Counter
SecureValueGetForDecryptDuration prometheus.Histogram
SecureValueSetExternalIDDuration prometheus.Histogram
SecureValueSetStatusDuration prometheus.Histogram
DecryptDuration *prometheus.HistogramVec
DecryptRequestCount *prometheus.CounterVec
}
func newStorageMetrics() *StorageMetrics {
return &StorageMetrics{
// Outbox metrics
OutboxAppendDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_append_duration_seconds",
Help: "Duration of outbox message append operations",
Buckets: prometheus.DefBuckets,
}, []string{"message_type"}),
OutboxAppendCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_append_count",
Help: "Count of outbox message append operations",
}, []string{"message_type"}),
OutboxReceiveDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_receive_duration_seconds",
Help: "Duration of outbox message receive operations",
Buckets: prometheus.DefBuckets,
}),
OutboxReceiveCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_receive_count",
Help: "Count of outbox message receive operations",
}),
OutboxDeleteDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_delete_duration_seconds",
Help: "Duration of outbox message delete operations",
Buckets: prometheus.DefBuckets,
}),
OutboxDeleteCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_delete_count",
Help: "Count of outbox message delete operations",
}),
OutboxIncrementReceiveCountDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_increment_receive_count_duration_seconds",
Help: "Duration of outbox message increment receive count operations",
Buckets: prometheus.DefBuckets,
}),
OutboxIncrementReceiveCountCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_increment_receive_count_count",
Help: "Count of outbox message increment receive count operations",
}),
OutboxTotalMessageLifetimeDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "outbox_total_message_lifetime_duration_seconds",
Help: "Total duration of outbox message lifetime",
Buckets: prometheus.DefBuckets,
}, []string{"message_type"}),
// Keeper metrics
KeeperMetadataCreateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_create_duration_seconds",
Help: "Duration of keeper metadata create operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
KeeperMetadataCreateCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_create_count",
Help: "Count of keeper metadata create operations",
}, []string{"keeper_type"}),
KeeperMetadataUpdateDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_update_duration_seconds",
Help: "Duration of keeper metadata update operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
KeeperMetadataUpdateCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_update_count",
Help: "Count of keeper metadata update operations",
}, []string{"keeper_type"}),
KeeperMetadataDeleteDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_delete_duration_seconds",
Help: "Duration of keeper metadata delete operations",
Buckets: prometheus.DefBuckets,
}),
KeeperMetadataDeleteCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_delete_count",
Help: "Count of keeper metadata delete operations",
}),
KeeperMetadataGetDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_get_duration_seconds",
Help: "Duration of keeper metadata get operations",
Buckets: prometheus.DefBuckets,
}, []string{"keeper_type"}),
KeeperMetadataGetCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_get_count",
Help: "Count of keeper metadata get operations",
}, []string{"keeper_type"}),
KeeperMetadataListDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_list_duration_seconds",
Help: "Duration of keeper metadata list operations",
Buckets: prometheus.DefBuckets,
}),
KeeperMetadataListCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_list_count",
Help: "Count of keeper metadata list operations",
}),
KeeperMetadataGetKeeperConfigDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "keeper_metadata_get_keeper_config_duration_seconds",
Help: "Duration of keeper metadata get keeper config operations",
Buckets: prometheus.DefBuckets,
}),
// Secure value metrics
SecureValueMetadataCreateDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_create_duration_seconds",
Help: "Duration of secure value metadata create operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueMetadataCreateCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_create_count",
Help: "Count of secure value metadata create operations",
}),
SecureValueMetadataUpdateDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_update_duration_seconds",
Help: "Duration of secure value metadata update operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueMetadataUpdateCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_update_count",
Help: "Count of secure value metadata update operations",
}),
SecureValueMetadataDeleteDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_delete_duration_seconds",
Help: "Duration of secure value metadata delete operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueMetadataDeleteCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_delete_count",
Help: "Count of secure value metadata delete operations",
}),
SecureValueMetadataGetDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_get_duration_seconds",
Help: "Duration of secure value metadata get operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueMetadataGetCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_get_count",
Help: "Count of secure value metadata get operations",
}),
SecureValueMetadataListDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_list_duration_seconds",
Help: "Duration of secure value metadata list operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueMetadataListCount: prometheus.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_metadata_list_count",
Help: "Count of secure value metadata list operations",
}),
SecureValueGetForDecryptDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_get_for_decrypt_duration_seconds",
Help: "Duration of secure value get for decrypt operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueSetExternalIDDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_set_external_id_duration_seconds",
Help: "Duration of secure value set external id operations",
Buckets: prometheus.DefBuckets,
}),
SecureValueSetStatusDuration: prometheus.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "secure_value_set_status_duration_seconds",
Help: "Duration of secure value set status operations",
Buckets: prometheus.DefBuckets,
}),
// Decrypt metrics
DecryptDuration: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "decrypt_duration_seconds",
Help: "Duration of decrypt operations",
Buckets: prometheus.DefBuckets,
}, []string{"successful"}),
DecryptRequestCount: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Name: "decrypt_request_count",
Help: "Count of decrypt operations",
}, []string{"successful"}),
}
}
var (
initOnce sync.Once
metricsInstance *StorageMetrics
)
// NewStorageMetrics returns a singleton instance of the SecretsMetrics struct containing registered metrics
func NewStorageMetrics(reg prometheus.Registerer) *StorageMetrics {
initOnce.Do(func() {
m := newStorageMetrics()
if reg != nil {
reg.MustRegister(
m.OutboxAppendDuration,
m.OutboxAppendCount,
m.OutboxReceiveDuration,
m.OutboxReceiveCount,
m.OutboxDeleteDuration,
m.OutboxDeleteCount,
m.OutboxIncrementReceiveCountDuration,
m.OutboxIncrementReceiveCountCount,
m.OutboxTotalMessageLifetimeDuration,
m.KeeperMetadataCreateDuration,
m.KeeperMetadataCreateCount,
m.KeeperMetadataUpdateDuration,
m.KeeperMetadataUpdateCount,
m.KeeperMetadataDeleteDuration,
m.KeeperMetadataDeleteCount,
m.KeeperMetadataGetDuration,
m.KeeperMetadataGetCount,
m.KeeperMetadataListDuration,
m.KeeperMetadataListCount,
m.KeeperMetadataGetKeeperConfigDuration,
m.SecureValueMetadataCreateDuration,
m.SecureValueMetadataCreateCount,
m.SecureValueMetadataUpdateDuration,
m.SecureValueMetadataUpdateCount,
m.SecureValueMetadataDeleteDuration,
m.SecureValueMetadataDeleteCount,
m.SecureValueMetadataGetDuration,
m.SecureValueMetadataGetCount,
m.SecureValueMetadataListDuration,
m.SecureValueMetadataListCount,
m.SecureValueGetForDecryptDuration,
m.SecureValueSetExternalIDDuration,
m.SecureValueSetStatusDuration,
m.DecryptDuration,
m.DecryptRequestCount,
)
}
metricsInstance = m
})
return metricsInstance
}
func NewTestMetrics() *StorageMetrics {
return newStorageMetrics()
}
@@ -6,7 +6,9 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/grafana/grafana/pkg/storage/secret/metadata/metrics"
unifiedsql "github.com/grafana/grafana/pkg/storage/unified/sql" unifiedsql "github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
@@ -20,15 +22,18 @@ type outboxStore struct {
db contracts.Database db contracts.Database
dialect sqltemplate.Dialect dialect sqltemplate.Dialect
tracer trace.Tracer tracer trace.Tracer
metrics *metrics.StorageMetrics
} }
func ProvideOutboxQueue( func ProvideOutboxQueue(
db contracts.Database, db contracts.Database,
tracer trace.Tracer, tracer trace.Tracer,
reg prometheus.Registerer,
) contracts.OutboxQueue { ) contracts.OutboxQueue {
return &outboxStore{ return &outboxStore{
db: db, db: db,
dialect: sqltemplate.DialectForDriver(db.DriverName()), dialect: sqltemplate.DialectForDriver(db.DriverName()),
metrics: metrics.NewStorageMetrics(reg),
tracer: tracer, tracer: tracer,
} }
} }
@@ -47,6 +52,7 @@ type outboxMessageDB struct {
} }
func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (messageID int64, err error) { func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMessage) (messageID int64, err error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "outboxStore.Append", trace.WithAttributes(
attribute.String("name", input.Name), attribute.String("name", input.Name),
attribute.String("namespace", input.Namespace), attribute.String("namespace", input.Namespace),
@@ -73,6 +79,9 @@ func (s *outboxStore) Append(ctx context.Context, input contracts.AppendOutboxMe
return messageID, fmt.Errorf("inserting message into outbox table: %+w", err) return messageID, fmt.Errorf("inserting message into outbox table: %+w", err)
} }
s.metrics.OutboxAppendDuration.WithLabelValues(string(input.Type)).Observe(time.Since(start).Seconds())
s.metrics.OutboxAppendCount.WithLabelValues(string(input.Type)).Inc()
return messageID, nil return messageID, nil
} }
@@ -147,6 +156,7 @@ func (s *outboxStore) insertMessage(ctx context.Context, input contracts.AppendO
} }
func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.OutboxMessage, error) { func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.OutboxMessage, error) {
start := time.Now()
messageIDs, err := s.fetchMessageIdsInQueue(ctx, limit) messageIDs, err := s.fetchMessageIdsInQueue(ctx, limit)
if err != nil { if err != nil {
return nil, fmt.Errorf("fetching message ids from queue: %w", err) return nil, fmt.Errorf("fetching message ids from queue: %w", err)
@@ -223,6 +233,9 @@ func (s *outboxStore) ReceiveN(ctx context.Context, limit uint) ([]contracts.Out
return messages, fmt.Errorf("reading rows: %w", err) return messages, fmt.Errorf("reading rows: %w", err)
} }
s.metrics.OutboxReceiveDuration.Observe(time.Since(start).Seconds())
s.metrics.OutboxReceiveCount.Add(float64(len(messages)))
return messages, nil return messages, nil
} }
@@ -275,10 +288,14 @@ func (s *outboxStore) Delete(ctx context.Context, messageID int64) (err error) {
assert.True(messageID != 0, "outboxStore.Delete: messageID is required") assert.True(messageID != 0, "outboxStore.Delete: messageID is required")
start := time.Now()
if err := s.deleteMessage(ctx, messageID); err != nil { if err := s.deleteMessage(ctx, messageID); err != nil {
return fmt.Errorf("deleting message from outbox table %+w", err) return fmt.Errorf("deleting message from outbox table %+w", err)
} }
s.metrics.OutboxDeleteDuration.Observe(time.Since(start).Seconds())
s.metrics.OutboxDeleteCount.Inc()
return nil return nil
} }
@@ -320,6 +337,9 @@ func (s *outboxStore) deleteMessage(ctx context.Context, messageID int64) error
return fmt.Errorf("rows error: %w", err) return fmt.Errorf("rows error: %w", err)
} }
totalLifetime := time.Since(time.UnixMilli(timestamp))
s.metrics.OutboxTotalMessageLifetimeDuration.WithLabelValues(messageType).Observe(totalLifetime.Seconds())
// Then delete the object // Then delete the object
delReq := deleteSecureValueOutbox{ delReq := deleteSecureValueOutbox{
SQLTemplate: sqltemplate.New(s.dialect), SQLTemplate: sqltemplate.New(s.dialect),
@@ -359,6 +379,7 @@ func (s *outboxStore) IncrementReceiveCount(ctx context.Context, messageIDs []in
MessageIDs: messageIDs, MessageIDs: messageIDs,
} }
start := time.Now()
query, err := sqltemplate.Execute(sqlSecureValueOutboxUpdateReceiveCount, req) query, err := sqltemplate.Execute(sqlSecureValueOutboxUpdateReceiveCount, req)
if err != nil { if err != nil {
return fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxUpdateReceiveCount.Name(), err) return fmt.Errorf("execute template %q: %w", sqlSecureValueOutboxUpdateReceiveCount.Name(), err)
@@ -369,5 +390,8 @@ func (s *outboxStore) IncrementReceiveCount(ctx context.Context, messageIDs []in
return fmt.Errorf("updating outbox messages receive count: %w", err) return fmt.Errorf("updating outbox messages receive count: %w", err)
} }
s.metrics.OutboxIncrementReceiveCountDuration.Observe(time.Since(start).Seconds())
s.metrics.OutboxIncrementReceiveCountCount.Add(float64(len(messageIDs)))
return nil return nil
} }
@@ -39,7 +39,7 @@ func (model *outboxStoreModel) Append(messageID int64, message contracts.AppendO
func (model *outboxStoreModel) ReceiveN(n uint) []contracts.OutboxMessage { func (model *outboxStoreModel) ReceiveN(n uint) []contracts.OutboxMessage {
maxMessages := min(len(model.rows), int(n)) maxMessages := min(len(model.rows), int(n))
if maxMessages == 0 { if maxMessages == 0 {
return []contracts.OutboxMessage{} return nil
} }
return model.rows[:maxMessages] return model.rows[:maxMessages]
} }
@@ -115,7 +115,7 @@ func TestOutboxStoreSecureValueOperationInProgress(t *testing.T) {
ctx := context.Background() ctx := context.Background()
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
_, err := outbox.Append(ctx, contracts.AppendOutboxMessage{ _, err := outbox.Append(ctx, contracts.AppendOutboxMessage{
RequestID: "1", RequestID: "1",
@@ -148,7 +148,7 @@ func TestOutboxStore(t *testing.T) {
ctx := context.Background() ctx := context.Background()
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
m1 := contracts.AppendOutboxMessage{ m1 := contracts.AppendOutboxMessage{
Type: contracts.CreateSecretOutboxMessage, Type: contracts.CreateSecretOutboxMessage,
@@ -216,7 +216,7 @@ func TestOutboxStoreProperty(t *testing.T) {
testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New())) testDB := sqlstore.NewTestStore(t, sqlstore.WithMigrator(migrator.New()))
tracer := noop.NewTracerProvider().Tracer("test") tracer := noop.NewTracerProvider().Tracer("test")
outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer) outbox := ProvideOutboxQueue(database.ProvideDatabase(testDB, tracer), tracer, nil)
model := newOutboxStoreModel() model := newOutboxStoreModel()
@@ -3,20 +3,28 @@ package metadata
import ( import (
"context" "context"
"fmt" "fmt"
"time"
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1" secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts" "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube" "github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/storage/secret/metadata/metrics"
"github.com/grafana/grafana/pkg/storage/unified/sql" "github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
) )
var _ contracts.SecureValueMetadataStorage = (*secureValueMetadataStorage)(nil) var _ contracts.SecureValueMetadataStorage = (*secureValueMetadataStorage)(nil)
func ProvideSecureValueMetadataStorage(db contracts.Database, tracer trace.Tracer, features featuremgmt.FeatureToggles) (contracts.SecureValueMetadataStorage, error) { func ProvideSecureValueMetadataStorage(
db contracts.Database,
tracer trace.Tracer,
features featuremgmt.FeatureToggles,
reg prometheus.Registerer,
) (contracts.SecureValueMetadataStorage, error) {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) || if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) ||
!features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) { !features.IsEnabledGlobally(featuremgmt.FlagSecretsManagementAppPlatform) {
return &secureValueMetadataStorage{}, nil return &secureValueMetadataStorage{}, nil
@@ -25,6 +33,7 @@ func ProvideSecureValueMetadataStorage(db contracts.Database, tracer trace.Trace
return &secureValueMetadataStorage{ return &secureValueMetadataStorage{
db: db, db: db,
dialect: sqltemplate.DialectForDriver(db.DriverName()), dialect: sqltemplate.DialectForDriver(db.DriverName()),
metrics: metrics.NewStorageMetrics(reg),
tracer: tracer, tracer: tracer,
}, nil }, nil
} }
@@ -33,10 +42,12 @@ func ProvideSecureValueMetadataStorage(db contracts.Database, tracer trace.Trace
type secureValueMetadataStorage struct { type secureValueMetadataStorage struct {
db contracts.Database db contracts.Database
dialect sqltemplate.Dialect dialect sqltemplate.Dialect
metrics *metrics.StorageMetrics
tracer trace.Tracer tracer trace.Tracer
} }
func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) { func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Create", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Create", trace.WithAttributes(
attribute.String("name", sv.GetName()), attribute.String("name", sv.GetName()),
attribute.String("namespace", sv.GetNamespace()), attribute.String("namespace", sv.GetNamespace()),
@@ -117,10 +128,14 @@ func (s *secureValueMetadataStorage) Create(ctx context.Context, sv *secretv0alp
return nil, fmt.Errorf("convert to kubernetes object: %w", err) return nil, fmt.Errorf("convert to kubernetes object: %w", err)
} }
s.metrics.SecureValueMetadataCreateDuration.Observe(time.Since(start).Seconds())
s.metrics.SecureValueMetadataCreateCount.Inc()
return createdSecureValue, nil return createdSecureValue, nil
} }
func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) { func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.Namespace, name string, opts contracts.ReadOpts) (*secretv0alpha1.SecureValue, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Read", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Read", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -138,10 +153,14 @@ func (s *secureValueMetadataStorage) Read(ctx context.Context, namespace xkube.N
return nil, fmt.Errorf("convert to kubernetes object: %w", err) return nil, fmt.Errorf("convert to kubernetes object: %w", err)
} }
s.metrics.SecureValueMetadataGetDuration.Observe(time.Since(start).Seconds())
s.metrics.SecureValueMetadataGetCount.Inc()
return secureValueKub, nil return secureValueKub, nil
} }
func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) { func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue *secretv0alpha1.SecureValue, actorUID string) (*secretv0alpha1.SecureValue, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Update", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Update", trace.WithAttributes(
attribute.String("name", newSecureValue.GetName()), attribute.String("name", newSecureValue.GetName()),
attribute.String("namespace", newSecureValue.GetNamespace()), attribute.String("namespace", newSecureValue.GetNamespace()),
@@ -228,10 +247,14 @@ func (s *secureValueMetadataStorage) Update(ctx context.Context, newSecureValue
return nil, fmt.Errorf("convert to kubernetes object: %w", err) return nil, fmt.Errorf("convert to kubernetes object: %w", err)
} }
s.metrics.SecureValueMetadataUpdateDuration.Observe(time.Since(start).Seconds())
s.metrics.SecureValueMetadataUpdateCount.Inc()
return secureValue, nil return secureValue, nil
} }
func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error { func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string) error {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Delete", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.Delete", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -258,10 +281,14 @@ func (s *secureValueMetadataStorage) Delete(ctx context.Context, namespace xkube
return fmt.Errorf("deleting secure value rowsAffected=%d error=%w", rowsAffected, err) return fmt.Errorf("deleting secure value rowsAffected=%d error=%w", rowsAffected, err)
} }
s.metrics.SecureValueMetadataDeleteDuration.Observe(time.Since(start).Seconds())
s.metrics.SecureValueMetadataDeleteCount.Inc()
return nil return nil
} }
func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (svList []secretv0alpha1.SecureValue, error error) { func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.Namespace) (svList []secretv0alpha1.SecureValue, error error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.List", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.List", trace.WithAttributes(
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
)) ))
@@ -316,10 +343,14 @@ func (s *secureValueMetadataStorage) List(ctx context.Context, namespace xkube.N
return nil, fmt.Errorf("read rows error: %w", err) return nil, fmt.Errorf("read rows error: %w", err)
} }
s.metrics.SecureValueMetadataListDuration.Observe(time.Since(start).Seconds())
s.metrics.SecureValueMetadataListCount.Inc()
return secureValues, nil return secureValues, nil
} }
func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error { func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespace xkube.Namespace, name string, externalID contracts.ExternalID) error {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetExternalID", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -352,10 +383,13 @@ func (s *secureValueMetadataStorage) SetExternalID(ctx context.Context, namespac
if modifiedCount > 1 { if modifiedCount > 1 {
return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount) return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount)
} }
s.metrics.SecureValueSetExternalIDDuration.Observe(time.Since(start).Seconds())
return nil return nil
} }
func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error { func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xkube.Namespace, name string, status secretv0alpha1.SecureValueStatus) error {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetStatus", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.SetStatus", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -391,10 +425,13 @@ func (s *secureValueMetadataStorage) SetStatus(ctx context.Context, namespace xk
if modifiedCount > 1 { if modifiedCount > 1 {
return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount) return fmt.Errorf("secureValueMetadataStorage.SetExternalID: modified more than one secret, this is a bug, check the where condition: modifiedCount=%d", modifiedCount)
} }
s.metrics.SecureValueSetStatusDuration.Observe(time.Since(start).Seconds())
return nil return nil
} }
func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) { func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespace xkube.Namespace, name string) (*contracts.DecryptSecureValue, error) {
start := time.Now()
ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.ReadForDecrypt", trace.WithAttributes( ctx, span := s.tracer.Start(ctx, "SecureValueMetadataStorage.ReadForDecrypt", trace.WithAttributes(
attribute.String("name", name), attribute.String("name", name),
attribute.String("namespace", namespace.String()), attribute.String("namespace", namespace.String()),
@@ -437,6 +474,8 @@ func (s *secureValueMetadataStorage) ReadForDecrypt(ctx context.Context, namespa
return nil, fmt.Errorf("convert to kubernetes object: %w", err) return nil, fmt.Errorf("convert to kubernetes object: %w", err)
} }
s.metrics.SecureValueGetForDecryptDuration.Observe(time.Since(start).Seconds())
return secureValue, nil return secureValue, nil
} }
@@ -43,11 +43,11 @@ func Test_SecureValueMetadataStorage_CreateAndRead(t *testing.T) {
features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform) features := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, featuremgmt.FlagSecretsManagementAppPlatform)
// Initialize the secure value storage // Initialize the secure value storage
secureValueStorage, err := ProvideSecureValueMetadataStorage(db, tracer, features) secureValueStorage, err := ProvideSecureValueMetadataStorage(db, tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
// Initialize the keeper storage // Initialize the keeper storage
keeperStorage, err := ProvideKeeperMetadataStorage(db, tracer, features) keeperStorage, err := ProvideKeeperMetadataStorage(db, tracer, features, nil)
require.NoError(t, err) require.NoError(t, err)
t.Run("create and read a secure value", func(t *testing.T) { t.Run("create and read a secure value", func(t *testing.T) {
+2 -2
View File
@@ -133,8 +133,8 @@ func (*SecretDB) AddMigration(mg *migrator.Migrator) {
tables = append(tables, migrator.Table{ tables = append(tables, migrator.Table{
Name: TableNameSecureValueOutbox, Name: TableNameSecureValueOutbox,
Columns: []*migrator.Column{ Columns: []*migrator.Column{
{Name: "request_id", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, {Name: "request_id", Type: migrator.DB_NVarchar, Length: 1024, Nullable: false}, // Safer upper limit because we hex-encode traceparent+tracestate to form the request_id.
{Name: "id", Type: migrator.DB_BigInt, Length: 36, IsPrimaryKey: true, IsAutoIncrement: true}, // Fixed size of a UUID. {Name: "id", Type: migrator.DB_BigInt, Length: 36, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "message_type", Type: migrator.DB_NVarchar, Length: 16, Nullable: false}, {Name: "message_type", Type: migrator.DB_NVarchar, Length: 16, Nullable: false},
{Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s. {Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s.
{Name: "namespace", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s. {Name: "namespace", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, // Limit enforced by K8s.