mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Encryption: Add support for data keys re-encryption (#43548)
* Encryption: Add support for data keys re-encryption * Add tests for data keys re-encryption * Update code after refactorings Co-authored-by: Leonard Gram <leo@xlson.com>
This commit is contained in:
parent
16f0c6617a
commit
b2655750e8
@ -188,6 +188,11 @@ var adminCommands = []*cli.Command{
|
||||
Usage: "Rolls back secrets to legacy encryption. Returns ok unless there is an error. Safe to execute multiple times.",
|
||||
Action: runRunnerCommand(secretsmigrations.RollBackSecrets),
|
||||
},
|
||||
{
|
||||
Name: "re-encrypt-data-keys",
|
||||
Usage: "Rotates persisted data encryption keys. Returns ok unless there is an error. Safe to execute multiple times.",
|
||||
Action: runRunnerCommand(secretsmigrations.ReEncryptDEKS),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -0,0 +1,19 @@
|
||||
package secretsmigrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/runner"
|
||||
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
)
|
||||
|
||||
func ReEncryptDEKS(_ utils.CommandLine, runner runner.Runner) error {
|
||||
if !runner.Features.IsEnabled(featuremgmt.FlagEnvelopeEncryption) {
|
||||
logger.Warn("Envelope encryption is not enabled, quitting...")
|
||||
return nil
|
||||
}
|
||||
|
||||
return runner.SecretsService.ReEncryptDataKeys(context.Background())
|
||||
}
|
@ -87,3 +87,42 @@ func (ss *SecretsStoreImpl) DeleteDataKey(ctx context.Context, name string) erro
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (ss *SecretsStoreImpl) ReEncryptDataKeys(
|
||||
ctx context.Context,
|
||||
providers map[secrets.ProviderID]secrets.Provider,
|
||||
currProvider secrets.ProviderID,
|
||||
) error {
|
||||
return ss.sqlStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
keys := make([]*secrets.DataKey, 0)
|
||||
if err := sess.Table(dataKeysTable).Find(&keys); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
provider, ok := providers[k.Provider]
|
||||
if !ok {
|
||||
return fmt.Errorf("could not find encryption provider '%s'", k.Provider)
|
||||
}
|
||||
|
||||
decrypted, err := provider.Decrypt(ctx, k.EncryptedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Updating current data key by re-encrypting it with current provider.
|
||||
// Accessing the current provider within providers map should be safe.
|
||||
k.Provider = currProvider
|
||||
k.EncryptedData, err = providers[currProvider].Encrypt(ctx, decrypted)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := sess.Table(dataKeysTable).Where("name = ?", k.Name).Update(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -40,6 +40,10 @@ func (f FakeSecretsService) GetDecryptedValue(_ context.Context, sjd map[string]
|
||||
return fallback
|
||||
}
|
||||
|
||||
func (f FakeSecretsService) ReEncryptDataKeys(_ context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FakeSecretsService) CurrentProviderID() string {
|
||||
return "fakeProvider"
|
||||
}
|
||||
|
@ -45,3 +45,7 @@ func (f FakeSecretsStore) DeleteDataKey(_ context.Context, name string) error {
|
||||
delete(f.store, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f FakeSecretsStore) ReEncryptDataKeys(_ context.Context, _ map[secrets.ProviderID]secrets.Provider, _ secrets.ProviderID) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -353,6 +353,17 @@ func (s *SecretsService) GetProviders() map[secrets.ProviderID]secrets.Provider
|
||||
return s.providers
|
||||
}
|
||||
|
||||
func (s *SecretsService) ReEncryptDataKeys(ctx context.Context) error {
|
||||
err := s.store.ReEncryptDataKeys(ctx, s.providers, s.currentProviderID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Invalidate cache
|
||||
s.dataKeyCache = make(map[string]dataKeyCacheItem)
|
||||
return err
|
||||
}
|
||||
|
||||
// These variables are used to test the code
|
||||
// responsible for periodically cleaning up
|
||||
// data encryption keys cache.
|
||||
|
@ -319,3 +319,42 @@ func TestSecretsService_Run(t *testing.T) {
|
||||
assert.True(t, svc.dataKeyCache[dataKeyID].expiry.After(time.Now().Add(dekTTL)))
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecretsService_ReEncryptDataKeys(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
sql := sqlstore.InitTestDB(t)
|
||||
store := database.ProvideSecretsStore(sql)
|
||||
svc := SetupTestService(t, store)
|
||||
|
||||
// Encrypt to generate data encryption key
|
||||
withoutScope := secrets.WithoutScope()
|
||||
ciphertext, err := svc.Encrypt(ctx, []byte("grafana"), withoutScope)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("existing key should be re-encrypted", func(t *testing.T) {
|
||||
prevDataKeys, err := store.GetAllDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, prevDataKeys, 1)
|
||||
|
||||
err = svc.ReEncryptDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
reEncryptedDataKeys, err := store.GetAllDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, reEncryptedDataKeys, 1)
|
||||
|
||||
assert.NotEqual(t, prevDataKeys[0].EncryptedData, reEncryptedDataKeys[0].EncryptedData)
|
||||
})
|
||||
|
||||
t.Run("data keys cache should be invalidated", func(t *testing.T) {
|
||||
// Decrypt to ensure data key is cached
|
||||
_, err := svc.Decrypt(ctx, ciphertext)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, svc.dataKeyCache)
|
||||
|
||||
err = svc.ReEncryptDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, svc.dataKeyCache)
|
||||
})
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ type Service interface {
|
||||
DecryptJsonData(ctx context.Context, sjd map[string][]byte) (map[string]string, error)
|
||||
|
||||
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback string) string
|
||||
|
||||
ReEncryptDataKeys(ctx context.Context) error
|
||||
}
|
||||
|
||||
// Store defines methods to interact with secrets storage
|
||||
@ -33,6 +35,7 @@ type Store interface {
|
||||
CreateDataKey(ctx context.Context, dataKey DataKey) error
|
||||
CreateDataKeyWithDBSession(ctx context.Context, dataKey DataKey, sess *xorm.Session) error
|
||||
DeleteDataKey(ctx context.Context, name string) error
|
||||
ReEncryptDataKeys(ctx context.Context, providers map[ProviderID]Provider, currProvider ProviderID) error
|
||||
}
|
||||
|
||||
// Provider is a key encryption key provider for envelope encryption
|
||||
|
Loading…
Reference in New Issue
Block a user