diff --git a/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go b/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go index d78d3b9a5bd..3d6b84b22a1 100644 --- a/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go +++ b/pkg/cmd/grafana-cli/commands/secretsmigrations/secretsmigrations.go @@ -5,35 +5,16 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/runner" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/featuremgmt" ) -var logger = log.New("secrets.migrations") - func ReEncryptDEKS(_ utils.CommandLine, runner runner.Runner) error { - if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - logger.Warn("Envelope encryption is not enabled, quitting...") - return nil - } - return runner.SecretsService.ReEncryptDataKeys(context.Background()) } func ReEncryptSecrets(_ utils.CommandLine, runner runner.Runner) error { - if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - logger.Warn("Envelope encryption is not enabled, quitting...") - return nil - } - return runner.SecretsMigrator.ReEncryptSecrets(context.Background()) } func RollBackSecrets(_ utils.CommandLine, runner runner.Runner) error { - if runner.Features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - logger.Warn("Envelope encryption is not enabled, quitting...") - return nil - } - return runner.SecretsMigrator.RollBackSecrets(context.Background()) } diff --git a/pkg/services/secrets/manager/helpers.go b/pkg/services/secrets/manager/helpers.go index 022ebc3967f..33448e84362 100644 --- a/pkg/services/secrets/manager/helpers.go +++ b/pkg/services/secrets/manager/helpers.go @@ -14,6 +14,14 @@ import ( ) func SetupTestService(tb testing.TB, store secrets.Store) *SecretsService { + return setupTestService(tb, store, featuremgmt.WithFeatures()) +} + +func SetupDisabledTestService(tb testing.TB, store secrets.Store) *SecretsService { + return setupTestService(tb, store, featuremgmt.WithFeatures(featuremgmt.FlagDisableEnvelopeEncryption)) +} + +func setupTestService(tb testing.TB, store secrets.Store, features *featuremgmt.FeatureManager) *SecretsService { tb.Helper() defaultKey := "SdlklWklckeLS" if len(setting.SecretKey) > 0 { @@ -28,8 +36,6 @@ func SetupTestService(tb testing.TB, store secrets.Store) *SecretsService { data_keys_cache_cleanup_interval = 1ns`)) require.NoError(tb, err) - features := featuremgmt.WithFeatures() - cfg := &setting.Cfg{Raw: raw} settings := &setting.OSSImpl{Cfg: cfg} diff --git a/pkg/services/secrets/manager/manager.go b/pkg/services/secrets/manager/manager.go index 1c3c391f01e..5d7581a9da2 100644 --- a/pkg/services/secrets/manager/manager.go +++ b/pkg/services/secrets/manager/manager.go @@ -24,6 +24,10 @@ import ( "xorm.io/xorm" ) +const ( + keyIdDelimiter = '#' +) + type SecretsService struct { store secrets.Store enc encryption.Internal @@ -137,6 +141,14 @@ func (s *SecretsService) registerUsageMetrics() { }) } +func (s *SecretsService) providersInitialized() bool { + return len(s.providers) > 0 +} + +func (s *SecretsService) encryptedWithEnvelopeEncryption(payload []byte) bool { + return len(payload) > 0 && payload[0] == keyIdDelimiter +} + var b64 = base64.RawStdEncoding func (s *SecretsService) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) { @@ -178,8 +190,8 @@ func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byt prefix := make([]byte, b64.EncodedLen(len(id))+2) b64.Encode(prefix[1:], []byte(id)) - prefix[0] = '#' - prefix[len(prefix)-1] = '#' + prefix[0] = keyIdDelimiter + prefix[len(prefix)-1] = keyIdDelimiter blob := make([]byte, len(prefix)+len(encrypted)) copy(blob, prefix) @@ -316,19 +328,6 @@ func newRandomDataKey() ([]byte, error) { } func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, error) { - if len(payload) == 0 { - return nil, fmt.Errorf("unable to decrypt empty payload") - } - - // Use legacy encryption service if featuremgmt.FlagDisableEnvelopeEncryption toggle is on - if s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { - if len(payload) > 0 && payload[0] == '#' { - return nil, fmt.Errorf("failed to decrypt a secret encrypted with envelope encryption: envelope encryption is disabled") - } - return s.enc.Decrypt(ctx, payload, setting.SecretKey) - } - - // If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption var err error defer func() { opsCounter.With(prometheus.Labels{ @@ -341,14 +340,28 @@ func (s *SecretsService) Decrypt(ctx context.Context, payload []byte) ([]byte, e } }() + if len(payload) == 0 { + err = fmt.Errorf("unable to decrypt empty payload") + return nil, err + } + + // If encrypted with envelope encryption, the feature is disabled and + // no provider is initialized, then we throw an error. + if s.encryptedWithEnvelopeEncryption(payload) && + s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) && + !s.providersInitialized() { + err = fmt.Errorf("failed to decrypt a secret encrypted with envelope encryption: envelope encryption is disabled") + return nil, err + } + var dataKey []byte - if payload[0] != '#' { + if !s.encryptedWithEnvelopeEncryption(payload) { secretKey := s.settings.KeyValue("security", "secret_key").Value() dataKey = []byte(secretKey) } else { payload = payload[1:] - endOfKey := bytes.Index(payload, []byte{'#'}) + endOfKey := bytes.Index(payload, []byte{keyIdDelimiter}) if endOfKey == -1 { err = fmt.Errorf("could not find valid key id in encrypted payload") return nil, err @@ -479,14 +492,24 @@ func (s *SecretsService) RotateDataKeys(ctx context.Context) error { func (s *SecretsService) ReEncryptDataKeys(ctx context.Context) error { s.log.Info("Data keys re-encryption triggered") - err := s.store.ReEncryptDataKeys(ctx, s.providers, s.currentProviderID) - if err != nil { + + if s.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + s.log.Info("Envelope encryption is not enabled but trying to init providers anyway...") + + if err := s.InitProviders(); err != nil { + s.log.Error("Envelope encryption providers initialization failed", "error", err) + return err + } + } + + if err := s.store.ReEncryptDataKeys(ctx, s.providers, s.currentProviderID); err != nil { s.log.Error("Data keys re-encryption failed", "error", err) return err } s.dataKeyCache.flush() s.log.Info("Data keys re-encryption finished successfully") + return nil } diff --git a/pkg/services/secrets/manager/manager_test.go b/pkg/services/secrets/manager/manager_test.go index 85ba4d259ec..159fda5c3e8 100644 --- a/pkg/services/secrets/manager/manager_test.go +++ b/pkg/services/secrets/manager/manager_test.go @@ -69,21 +69,6 @@ func TestSecretsService_EnvelopeEncryption(t *testing.T) { assert.Equal(t, len(keys), 2) }) - t.Run("decrypting empty payload should return error", func(t *testing.T) { - _, err := svc.Decrypt(context.Background(), []byte("")) - require.Error(t, err) - - assert.Equal(t, "unable to decrypt empty payload", err.Error()) - }) - - t.Run("decrypting legacy secret encrypted with secret key from settings", func(t *testing.T) { - expected := "grafana" - encrypted := []byte{122, 56, 53, 113, 101, 117, 73, 89, 20, 254, 36, 112, 112, 16, 128, 232, 227, 52, 166, 108, 192, 5, 28, 125, 126, 42, 197, 190, 251, 36, 94} - decrypted, err := svc.Decrypt(context.Background(), encrypted) - require.NoError(t, err) - assert.Equal(t, expected, string(decrypted)) - }) - t.Run("usage stats should be registered", func(t *testing.T) { reports, err := svc.usageStats.GetUsageReport(context.Background()) require.NoError(t, err) @@ -346,3 +331,65 @@ func TestSecretsService_ReEncryptDataKeys(t *testing.T) { assert.Empty(t, svc.dataKeyCache.byLabel) }) } + +func TestSecretsService_Decrypt(t *testing.T) { + ctx := context.Background() + store := database.ProvideSecretsStore(sqlstore.InitTestDB(t)) + + t.Run("empty payload should fail", func(t *testing.T) { + svc := SetupTestService(t, store) + _, err := svc.Decrypt(context.Background(), []byte("")) + require.Error(t, err) + + assert.Equal(t, "unable to decrypt empty payload", err.Error()) + }) + + t.Run("ee encrypted payload with ee disabled should fail", func(t *testing.T) { + svc := SetupTestService(t, store) + ciphertext, err := svc.Encrypt(ctx, []byte("grafana"), secrets.WithoutScope()) + require.NoError(t, err) + + svc = SetupDisabledTestService(t, store) + + _, err = svc.Decrypt(ctx, ciphertext) + assert.Error(t, err) + }) + + t.Run("ee encrypted payload with providers initialized should work", func(t *testing.T) { + svc := SetupTestService(t, store) + ciphertext, err := svc.Encrypt(ctx, []byte("grafana"), secrets.WithoutScope()) + require.NoError(t, err) + + svc = SetupDisabledTestService(t, store) + err = svc.InitProviders() + require.NoError(t, err) + + plaintext, err := svc.Decrypt(ctx, ciphertext) + assert.NoError(t, err) + assert.Equal(t, []byte("grafana"), plaintext) + }) + + t.Run("ee encrypted payload with ee enabled should work", func(t *testing.T) { + svc := SetupTestService(t, store) + ciphertext, err := svc.Encrypt(ctx, []byte("grafana"), secrets.WithoutScope()) + require.NoError(t, err) + + plaintext, err := svc.Decrypt(ctx, ciphertext) + assert.NoError(t, err) + assert.Equal(t, []byte("grafana"), plaintext) + }) + + t.Run("legacy payload should always work", func(t *testing.T) { + encrypted := []byte{122, 56, 53, 113, 101, 117, 73, 89, 20, 254, 36, 112, 112, 16, 128, 232, 227, 52, 166, 108, 192, 5, 28, 125, 126, 42, 197, 190, 251, 36, 94} + + svc := SetupTestService(t, store) + decrypted, err := svc.Decrypt(context.Background(), encrypted) + require.NoError(t, err) + assert.Equal(t, []byte("grafana"), decrypted) + + svc = SetupDisabledTestService(t, store) + decrypted, err = svc.Decrypt(context.Background(), encrypted) + require.NoError(t, err) + assert.Equal(t, []byte("grafana"), decrypted) + }) +} diff --git a/pkg/services/secrets/migrator/migrator.go b/pkg/services/secrets/migrator/migrator.go index 1db1f88ce99..2fcba755d2d 100644 --- a/pkg/services/secrets/migrator/migrator.go +++ b/pkg/services/secrets/migrator/migrator.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/encryption" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/setting" @@ -17,6 +18,7 @@ type SecretsMigrator struct { secretsSrv *manager.SecretsService sqlStore *sqlstore.SQLStore settings setting.Provider + features featuremgmt.FeatureToggles } func ProvideSecretsMigrator( @@ -24,16 +26,23 @@ func ProvideSecretsMigrator( service *manager.SecretsService, sqlStore *sqlstore.SQLStore, settings setting.Provider, + features featuremgmt.FeatureToggles, ) *SecretsMigrator { return &SecretsMigrator{ encryptionSrv: encryptionSrv, secretsSrv: service, sqlStore: sqlStore, settings: settings, + features: features, } } func (m *SecretsMigrator) ReEncryptSecrets(ctx context.Context) error { + err := m.initProvidersIfNeeded() + if err != nil { + return err + } + toReencrypt := []interface { reencrypt(context.Context, *manager.SecretsService, *sqlstore.SQLStore) }{ @@ -54,7 +63,25 @@ func (m *SecretsMigrator) ReEncryptSecrets(ctx context.Context) error { return nil } +func (m *SecretsMigrator) initProvidersIfNeeded() error { + if m.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) { + logger.Info("Envelope encryption is not enabled but trying to init providers anyway...") + + if err := m.secretsSrv.InitProviders(); err != nil { + logger.Error("Envelope encryption providers initialization failed", "error", err) + return err + } + } + + return nil +} + func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) error { + err := m.initProvidersIfNeeded() + if err != nil { + return err + } + toRollback := []interface { rollback(context.Context, *manager.SecretsService, encryption.Internal, *sqlstore.SQLStore, string) bool }{