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:
Joan López de la Franca Beltran
2022-05-23 13:13:55 +02:00
committed by GitHub
co-authored by Tania
parent ae8c11bfa4
commit e43879e55d
21 changed files with 498 additions and 170 deletions
+27 -11
View File
@@ -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)
})
}