mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
a7509ba18b
commit
7b40322bbe
@ -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())
|
||||
}
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
}{
|
||||
|
Loading…
Reference in New Issue
Block a user