pass dek cache into encryption manager

This commit is contained in:
Michael Mandrus
2025-11-13 15:33:25 -05:00
parent c9e381b96f
commit 773baf47e1
11 changed files with 227 additions and 5 deletions
-1
View File
@@ -58,5 +58,4 @@ import (
_ "github.com/grafana/grafana/apps/alerting/alertenrichment/pkg/apis/alertenrichment/v1beta1"
_ "github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
_ "github.com/testcontainers/testcontainers-go"
)
@@ -0,0 +1,104 @@
package manager
import (
"strconv"
"sync"
"time"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
)
type ossDataKeyCache struct {
mtx sync.RWMutex
byId map[string]*encryption.DataKeyCacheEntry
byLabel map[string]*encryption.DataKeyCacheEntry
cacheTTL time.Duration
}
func ProvideOSSDataKeyCache(cfg *setting.Cfg) encryption.DataKeyCache {
return &ossDataKeyCache{
byId: make(map[string]*encryption.DataKeyCacheEntry),
byLabel: make(map[string]*encryption.DataKeyCacheEntry),
cacheTTL: cfg.SecretsManagement.DataKeysCacheTTL,
}
}
func (c *ossDataKeyCache) GetById(id string) (*encryption.DataKeyCacheEntry, bool) {
c.mtx.RLock()
defer c.mtx.RUnlock()
entry, exists := c.byId[id]
cacheReadsCounter.With(prometheus.Labels{
"hit": strconv.FormatBool(exists),
"method": "byId",
}).Inc()
if !exists || entry.IsExpired() {
return nil, false
}
return entry, true
}
func (c *ossDataKeyCache) GetByLabel(label string) (*encryption.DataKeyCacheEntry, bool) {
c.mtx.RLock()
defer c.mtx.RUnlock()
entry, exists := c.byLabel[label]
cacheReadsCounter.With(prometheus.Labels{
"hit": strconv.FormatBool(exists),
"method": "byLabel",
}).Inc()
if !exists || entry.IsExpired() {
return nil, false
}
return entry, true
}
func (c *ossDataKeyCache) AddById(entry *encryption.DataKeyCacheEntry) {
c.mtx.Lock()
defer c.mtx.Unlock()
entry.Expiration = time.Now().Add(c.cacheTTL)
c.byId[entry.Id] = entry
}
func (c *ossDataKeyCache) AddByLabel(entry *encryption.DataKeyCacheEntry) {
c.mtx.Lock()
defer c.mtx.Unlock()
entry.Expiration = time.Now().Add(c.cacheTTL)
c.byLabel[entry.Label] = entry
}
func (c *ossDataKeyCache) RemoveExpired() {
c.mtx.Lock()
defer c.mtx.Unlock()
for id, entry := range c.byId {
if entry.IsExpired() {
delete(c.byId, id)
}
}
for label, entry := range c.byLabel {
if entry.IsExpired() {
delete(c.byLabel, label)
}
}
}
func (c *ossDataKeyCache) Flush() {
c.mtx.Lock()
c.byId = make(map[string]*encryption.DataKeyCacheEntry)
c.byLabel = make(map[string]*encryption.DataKeyCacheEntry)
c.mtx.Unlock()
}
@@ -7,11 +7,13 @@ import (
"fmt"
"strconv"
"sync"
"time"
"github.com/prometheus/client_golang/prometheus"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/usagestats"
@@ -19,6 +21,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher"
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@@ -26,6 +29,9 @@ type EncryptionManager struct {
tracer trace.Tracer
store contracts.DataKeyStorage
usageStats usagestats.Service
cfg *setting.Cfg
dataKeyCache encryption.DataKeyCache
mtx sync.Mutex
@@ -44,6 +50,8 @@ func ProvideEncryptionManager(
usageStats usagestats.Service,
enc cipher.Cipher,
providerConfig encryption.ProviderConfig,
dataKeyCache encryption.DataKeyCache,
cfg *setting.Cfg,
) (contracts.EncryptionManager, error) {
currentProviderID := providerConfig.CurrentProvider
if _, ok := providerConfig.AvailableProviders[currentProviderID]; !ok {
@@ -57,6 +65,7 @@ func ProvideEncryptionManager(
cipher: enc,
log: log.New("encryption"),
providerConfig: providerConfig,
cfg: cfg,
}
s.registerUsageMetrics()
@@ -327,3 +336,29 @@ func (s *EncryptionManager) dataKeyById(ctx context.Context, namespace, id strin
func (s *EncryptionManager) GetProviders() encryption.ProviderConfig {
return s.providerConfig
}
func (s *EncryptionManager) Run(ctx context.Context) error {
gc := time.NewTicker(
s.cfg.SecretsManagement.DataKeysCacheCleanupInterval,
)
grp, gCtx := errgroup.WithContext(ctx)
for {
select {
case <-gc.C:
s.log.Debug("Removing expired data keys from cache...")
s.dataKeyCache.RemoveExpired()
s.log.Debug("Removing expired data keys from cache finished successfully")
case <-gCtx.Done():
s.log.Debug("Grafana is shutting down; stopping...")
gc.Stop()
if err := grp.Wait(); err != nil && !errors.Is(err, context.Canceled) {
return err
}
return nil
}
}
}
@@ -201,6 +201,8 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfg,
)
require.NoError(t, err)
@@ -226,6 +228,8 @@ func TestEncryptionService_UseCurrentProvider(t *testing.T) {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfg,
)
require.NoError(t, err)
@@ -275,6 +279,8 @@ func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfgV1,
)
require.NoError(t, err)
@@ -313,6 +319,8 @@ func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) {
usageStats,
enc,
ossProvidersV2,
&NoopDataKeyCache{},
cfgV2,
)
require.NoError(t, err)
@@ -368,6 +376,8 @@ func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfgV1,
)
require.NoError(t, err)
@@ -392,6 +402,8 @@ func TestEncryptionService_SecretKeyVersionUpgrade(t *testing.T) {
usageStats,
enc,
ossProvidersV2,
&NoopDataKeyCache{},
cfgV2,
)
require.NoError(t, err)
@@ -573,6 +585,8 @@ func TestIntegration_SecretsService(t *testing.T) {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfg,
)
require.NoError(t, err)
@@ -610,6 +624,8 @@ func TestEncryptionService_ThirdPartyProviders(t *testing.T) {
enc, err := service.ProvideAESGCMCipherService(tracer, usageStats)
require.NoError(t, err)
cfg := &setting.Cfg{}
svc, err := ProvideEncryptionManager(
tracer,
nil,
@@ -621,6 +637,8 @@ func TestEncryptionService_ThirdPartyProviders(t *testing.T) {
encryption.ProviderID("fakeProvider.v1"): &fakeProvider{},
},
},
&NoopDataKeyCache{},
cfg,
)
require.NoError(t, err)
@@ -7,6 +7,7 @@ import (
"go.opentelemetry.io/otel/trace/noop"
"github.com/grafana/grafana/pkg/infra/usagestats"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption/cipher/service"
osskmsproviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
"github.com/grafana/grafana/pkg/services/sqlstore"
@@ -47,8 +48,32 @@ func setupTestService(tb testing.TB) *EncryptionManager {
usageStats,
enc,
ossProviders,
&NoopDataKeyCache{},
cfg,
)
require.NoError(tb, err)
return encMgr.(*EncryptionManager)
}
type NoopDataKeyCache struct {
}
func (c *NoopDataKeyCache) GetById(id string) (*encryption.DataKeyCacheEntry, bool) {
return nil, false
}
func (c *NoopDataKeyCache) GetByLabel(label string) (*encryption.DataKeyCacheEntry, bool) {
return nil, false
}
func (c *NoopDataKeyCache) AddById(entry *encryption.DataKeyCacheEntry) {
}
func (c *NoopDataKeyCache) AddByLabel(entry *encryption.DataKeyCacheEntry) {
}
func (c *NoopDataKeyCache) RemoveExpired() {
}
func (c *NoopDataKeyCache) Flush() {}
@@ -40,3 +40,25 @@ func (id ProviderID) Kind() (string, error) {
func KeyLabel(providerID ProviderID) string {
return fmt.Sprintf("%s@%s", time.Now().Format("2006-01-02"), providerID)
}
type DataKeyCache interface {
GetById(id string) (*DataKeyCacheEntry, bool)
GetByLabel(label string) (*DataKeyCacheEntry, bool)
AddById(entry *DataKeyCacheEntry)
AddByLabel(entry *DataKeyCacheEntry)
RemoveExpired()
Flush()
}
type DataKeyCacheEntry struct {
Namespace string
Id string
Label string
DataKey []byte
Active bool
Expiration time.Time
}
func (e DataKeyCacheEntry) IsExpired() bool {
return e.Expiration.Before(time.Now())
}
@@ -62,7 +62,7 @@ func setupTestService(t *testing.T, cfg *setting.Cfg) (*OSSKeeperService, error)
ossProviders, err := osskmsproviders.ProvideOSSKMSProviders(cfg, enc)
require.NoError(t, err)
encryptionManager, err := manager.ProvideEncryptionManager(tracer, dataKeyStore, usageStats, enc, ossProviders)
encryptionManager, err := manager.ProvideEncryptionManager(tracer, dataKeyStore, usageStats, enc, ossProviders, &manager.NoopDataKeyCache{}, cfg)
require.NoError(t, err)
// Initialize the keeper service
@@ -120,6 +120,8 @@ func Setup(t *testing.T, opts ...func(*SetupConfig)) Sut {
usageStats,
enc,
ossProviders,
&manager.NoopDataKeyCache{},
cfg,
)
require.NoError(t, err)
+6 -3
View File
@@ -482,7 +482,8 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
if err != nil {
return nil, err
}
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig)
dataKeyCache := manager2.ProvideOSSDataKeyCache(cfg)
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig, dataKeyCache, cfg)
if err != nil {
return nil, err
}
@@ -1122,7 +1123,8 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
if err != nil {
return nil, err
}
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig)
dataKeyCache := manager2.ProvideOSSDataKeyCache(cfg)
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig, dataKeyCache, cfg)
if err != nil {
return nil, err
}
@@ -1658,7 +1660,8 @@ func InitializeForCLI(ctx context.Context, cfg *setting.Cfg) (Runner, error) {
if err != nil {
return Runner{}, err
}
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig)
dataKeyCache := manager2.ProvideOSSDataKeyCache(cfg)
encryptionManager, err := manager2.ProvideEncryptionManager(tracer, dataKeyStorage, usageStats, cipher, providerConfig, dataKeyCache, cfg)
if err != nil {
return Runner{}, err
}
+2
View File
@@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret"
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
gsmKMSProviders "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/kmsproviders"
gsmEncryptionManager "github.com/grafana/grafana/pkg/registry/apis/secret/encryption/manager"
"github.com/grafana/grafana/pkg/registry/apis/secret/secretkeeper"
secretService "github.com/grafana/grafana/pkg/registry/apis/secret/service"
"github.com/grafana/grafana/pkg/registry/apps/advisor"
@@ -148,6 +149,7 @@ var wireExtsBasicSet = wire.NewSet(
aggregatorrunner.ProvideNoopAggregatorConfigurator,
apisregistry.WireSetExts,
gsmKMSProviders.ProvideOSSKMSProviders,
gsmEncryptionManager.ProvideOSSDataKeyCache,
secret.ProvideSecureValueClient,
provisioningExtras,
configProviderExtras,
+12
View File
@@ -11,8 +11,16 @@ const (
)
type SecretsManagerSettings struct {
// Which encryption provider to use to encrypt any new secrets
CurrentEncryptionProvider string
// The time to live for decrypted data keys in memory
DataKeysCacheTTL time.Duration
// The interval to remove expired data keys from the cache
DataKeysCacheCleanupInterval time.Duration
// Whether to use a Redis cache for data keys instead of the in-memory cache
DataKeysCacheUseRedis bool
// ConfiguredKMSProviders is a map of KMS providers found in the config file. The keys are in the format of <provider>.<keyName>, and the values are a map of the properties in that section
// In OSS, the provider type can only be "secret_key". In Enterprise, it can additionally be one of: "aws_kms", "azure_keyvault", "google_kms", "hashicorp_vault"
ConfiguredKMSProviders map[string]map[string]string
@@ -66,6 +74,10 @@ func (cfg *Cfg) readSecretsManagerSettings() {
cfg.SecretsManagement.RunSecretsDBMigrations = secretsMgmt.Key("run_secrets_db_migrations").MustBool(true)
cfg.SecretsManagement.RunDataKeyMigration = secretsMgmt.Key("run_data_key_migration").MustBool(true)
cfg.SecretsManagement.DataKeysCacheUseRedis = secretsMgmt.Key("data_keys_cache_use_redis").MustBool(false)
cfg.SecretsManagement.DataKeysCacheTTL = secretsMgmt.Key("data_keys_cache_ttl").MustDuration(15 * time.Minute)
cfg.SecretsManagement.DataKeysCacheCleanupInterval = secretsMgmt.Key("data_keys_cache_cleanup_interval").MustDuration(1 * time.Minute)
// Extract available KMS providers from configuration sections
providers := make(map[string]map[string]string)
for _, section := range cfg.Raw.Sections() {