Encryption: Add support to run secrets migrations even when EE is disabled (#51705)

* Encryption: Move secrets migrations into secrets.Migrator

* Encryption: Refactor secrets.Service initialization

* Encryption: Add support to run secrets migrations even when EE is disabled

* Init EE providers on-demand (only when needed)

* Add multiple tests + some adjustments

* Apply feedback
This commit is contained in:
Joan López de la Franca Beltran 2022-07-15 18:33:34 +02:00 committed by GitHub
parent a7509ba18b
commit 7b40322bbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 139 additions and 55 deletions

View File

@ -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())
}

View File

@ -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}

View File

@ -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
}

View File

@ -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)
})
}

View File

@ -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
}{