Encryption: Add support for multiple data keys per day (#47765)
* Add database migrations * Use short uids as data key ids * Add support for manual data key rotation * Fix duplicated mutex unlocks * Fix migration * Manage current data keys per name * Adjust key re-encryption and test * Modify rename column migration for MySQL compatibility * Refactor secrets manager and data keys cache * Multiple o11y adjustments * Fix stats query * Apply suggestions from code review Co-authored-by: Tania <yalyna.ts@gmail.com> * Fix linter * Docs: Rotate data encryption keys API endpoint Co-authored-by: Tania <yalyna.ts@gmail.com>
This commit is contained in:
co-authored by
Tania
parent
ae8c11bfa4
commit
e43879e55d
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
@@ -97,7 +98,8 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
||||
store := database.ProvideSecretsStore(sqlstore.InitTestDB(t))
|
||||
ctx := context.Background()
|
||||
|
||||
dataKey := secrets.DataKey{
|
||||
dataKey := &secrets.DataKey{
|
||||
Id: util.GenerateShortUID(),
|
||||
Active: true,
|
||||
Name: "test1",
|
||||
Provider: "test",
|
||||
@@ -105,7 +107,7 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("querying for a DEK that does not exist", func(t *testing.T) {
|
||||
res, err := store.GetDataKey(ctx, dataKey.Name)
|
||||
res, err := store.GetDataKey(ctx, dataKey.Id)
|
||||
assert.ErrorIs(t, secrets.ErrDataKeyNotFound, err)
|
||||
assert.Nil(t, res)
|
||||
})
|
||||
@@ -114,16 +116,26 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
||||
err := store.CreateDataKey(ctx, dataKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := store.GetDataKey(ctx, dataKey.Name)
|
||||
res, err := store.GetDataKey(ctx, dataKey.Id)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataKey.EncryptedData, res.EncryptedData)
|
||||
assert.Equal(t, dataKey.Provider, res.Provider)
|
||||
assert.Equal(t, dataKey.Name, res.Name)
|
||||
assert.Equal(t, dataKey.Id, res.Id)
|
||||
assert.True(t, dataKey.Active)
|
||||
|
||||
current, err := store.GetCurrentDataKey(ctx, dataKey.Name)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, dataKey.EncryptedData, current.EncryptedData)
|
||||
assert.Equal(t, dataKey.Provider, current.Provider)
|
||||
assert.Equal(t, dataKey.Name, current.Name)
|
||||
assert.Equal(t, dataKey.Id, current.Id)
|
||||
assert.True(t, current.Active)
|
||||
})
|
||||
|
||||
t.Run("creating an inactive DEK", func(t *testing.T) {
|
||||
k := secrets.DataKey{
|
||||
k := &secrets.DataKey{
|
||||
Id: util.GenerateShortUID(),
|
||||
Active: false,
|
||||
Name: "test2",
|
||||
Provider: "test",
|
||||
@@ -138,7 +150,7 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
||||
assert.Nil(t, res)
|
||||
})
|
||||
|
||||
t.Run("deleting DEK when no name provided must fail", func(t *testing.T) {
|
||||
t.Run("deleting DEK when no id provided must fail", func(t *testing.T) {
|
||||
beforeDelete, err := store.GetAllDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
err = store.DeleteDataKey(ctx, "")
|
||||
@@ -150,10 +162,10 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("deleting a DEK", func(t *testing.T) {
|
||||
err := store.DeleteDataKey(ctx, dataKey.Name)
|
||||
err := store.DeleteDataKey(ctx, dataKey.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := store.GetDataKey(ctx, dataKey.Name)
|
||||
res, err := store.GetDataKey(ctx, dataKey.Id)
|
||||
assert.Equal(t, secrets.ErrDataKeyNotFound, err)
|
||||
assert.Nil(t, res)
|
||||
})
|
||||
@@ -280,7 +292,8 @@ func TestSecretsService_Run(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Data encryption key cache should contain one element
|
||||
require.Len(t, svc.dataKeyCache.entries, 1)
|
||||
require.Len(t, svc.dataKeyCache.byId, 1)
|
||||
require.Len(t, svc.dataKeyCache.byName, 1)
|
||||
|
||||
t.Cleanup(func() { now = time.Now })
|
||||
now = func() time.Time { return time.Now().Add(10 * time.Minute) }
|
||||
@@ -294,7 +307,8 @@ func TestSecretsService_Run(t *testing.T) {
|
||||
// Then, once the ticker has been triggered,
|
||||
// the cleanup process should have happened,
|
||||
// therefore the cache should be empty.
|
||||
require.Len(t, svc.dataKeyCache.entries, 0)
|
||||
require.Len(t, svc.dataKeyCache.byId, 0)
|
||||
require.Len(t, svc.dataKeyCache.byName, 0)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -328,11 +342,13 @@ func TestSecretsService_ReEncryptDataKeys(t *testing.T) {
|
||||
// Decrypt to ensure data key is cached
|
||||
_, err := svc.Decrypt(ctx, ciphertext)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, svc.dataKeyCache.entries)
|
||||
require.NotEmpty(t, svc.dataKeyCache.byId)
|
||||
require.NotEmpty(t, svc.dataKeyCache.byName)
|
||||
|
||||
err = svc.ReEncryptDataKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Empty(t, svc.dataKeyCache.entries)
|
||||
assert.Empty(t, svc.dataKeyCache.byId)
|
||||
assert.Empty(t, svc.dataKeyCache.byName)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user