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.",
|
Usage: "Rolls back secrets to legacy encryption. Returns ok unless there is an error. Safe to execute multiple times.",
|
||||||
Action: runRunnerCommand(secretsmigrations.RollBackSecrets),
|
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
|
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
|
return fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f FakeSecretsService) ReEncryptDataKeys(_ context.Context) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f FakeSecretsService) CurrentProviderID() string {
|
func (f FakeSecretsService) CurrentProviderID() string {
|
||||||
return "fakeProvider"
|
return "fakeProvider"
|
||||||
}
|
}
|
||||||
|
@ -45,3 +45,7 @@ func (f FakeSecretsStore) DeleteDataKey(_ context.Context, name string) error {
|
|||||||
delete(f.store, name)
|
delete(f.store, name)
|
||||||
return nil
|
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
|
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
|
// These variables are used to test the code
|
||||||
// responsible for periodically cleaning up
|
// responsible for periodically cleaning up
|
||||||
// data encryption keys cache.
|
// 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)))
|
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)
|
DecryptJsonData(ctx context.Context, sjd map[string][]byte) (map[string]string, error)
|
||||||
|
|
||||||
GetDecryptedValue(ctx context.Context, sjd map[string][]byte, key, fallback string) string
|
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
|
// Store defines methods to interact with secrets storage
|
||||||
@ -33,6 +35,7 @@ type Store interface {
|
|||||||
CreateDataKey(ctx context.Context, dataKey DataKey) error
|
CreateDataKey(ctx context.Context, dataKey DataKey) error
|
||||||
CreateDataKeyWithDBSession(ctx context.Context, dataKey DataKey, sess *xorm.Session) error
|
CreateDataKeyWithDBSession(ctx context.Context, dataKey DataKey, sess *xorm.Session) error
|
||||||
DeleteDataKey(ctx context.Context, name string) 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
|
// Provider is a key encryption key provider for envelope encryption
|
||||||
|
Loading…
Reference in New Issue
Block a user