mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Encryption: Fix multiple data keys migration (#49848)
* Add migration * Migrator: Extend support to rename columns * Fix getting current key * Fix column name in migration * Fix deks reencryption * Fix caching * Add back separate caches for byName and byPrefix * Do not concatenate prefix with uid * Rename DataKey struc fields * SQLStore: Add deprecation comments for breaking migrations * Add comment * Minor corrections Co-authored-by: Joan López de la Franca Beltran <joanjan14@gmail.com>
This commit is contained in:
@@ -40,15 +40,15 @@ type DataSourceService interface {
|
|||||||
DecryptedValues(ctx context.Context, ds *models.DataSource) (map[string]string, error)
|
DecryptedValues(ctx context.Context, ds *models.DataSource) (map[string]string, error)
|
||||||
|
|
||||||
// DecryptedValue decrypts the encrypted datasource secureJSONData identified by key
|
// DecryptedValue decrypts the encrypted datasource secureJSONData identified by key
|
||||||
// and returns the decryped value.
|
// and returns the decrypted value.
|
||||||
DecryptedValue(ctx context.Context, ds *models.DataSource, key string) (string, bool, error)
|
DecryptedValue(ctx context.Context, ds *models.DataSource, key string) (string, bool, error)
|
||||||
|
|
||||||
// DecryptedBasicAuthPassword decrypts the encrypted datasource basic authentication
|
// DecryptedBasicAuthPassword decrypts the encrypted datasource basic authentication
|
||||||
// password and returns the decryped value.
|
// password and returns the decrypted value.
|
||||||
DecryptedBasicAuthPassword(ctx context.Context, ds *models.DataSource) (string, error)
|
DecryptedBasicAuthPassword(ctx context.Context, ds *models.DataSource) (string, error)
|
||||||
|
|
||||||
// DecryptedPassword decrypts the encrypted datasource password and returns the
|
// DecryptedPassword decrypts the encrypted datasource password and returns the
|
||||||
// decryped value.
|
// decrypted value.
|
||||||
DecryptedPassword(ctx context.Context, ds *models.DataSource) (string, error)
|
DecryptedPassword(ctx context.Context, ds *models.DataSource) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func (ss *SecretsStoreImpl) GetDataKey(ctx context.Context, id string) (*secrets
|
|||||||
err := ss.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
err := ss.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
var err error
|
var err error
|
||||||
exists, err = sess.Table(dataKeysTable).
|
exists, err = sess.Table(dataKeysTable).
|
||||||
Where("id = ?", id).
|
Where("name = ?", id).
|
||||||
Get(dataKey)
|
Get(dataKey)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -49,14 +49,14 @@ func (ss *SecretsStoreImpl) GetDataKey(ctx context.Context, id string) (*secrets
|
|||||||
return dataKey, nil
|
return dataKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ss *SecretsStoreImpl) GetCurrentDataKey(ctx context.Context, name string) (*secrets.DataKey, error) {
|
func (ss *SecretsStoreImpl) GetCurrentDataKey(ctx context.Context, label string) (*secrets.DataKey, error) {
|
||||||
dataKey := &secrets.DataKey{}
|
dataKey := &secrets.DataKey{}
|
||||||
var exists bool
|
var exists bool
|
||||||
|
|
||||||
err := ss.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
err := ss.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||||
var err error
|
var err error
|
||||||
exists, err = sess.Table(dataKeysTable).
|
exists, err = sess.Table(dataKeysTable).
|
||||||
Where("name = ? AND active = ?", name, ss.sqlStore.Dialect.BooleanStr(true)).
|
Where("label = ? AND active = ?", label, ss.sqlStore.Dialect.BooleanStr(true)).
|
||||||
Get(dataKey)
|
Get(dataKey)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -66,7 +66,7 @@ func (ss *SecretsStoreImpl) GetCurrentDataKey(ctx context.Context, name string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed getting data key: %w", err)
|
return nil, fmt.Errorf("failed getting current data key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return dataKey, nil
|
return dataKey, nil
|
||||||
@@ -137,7 +137,7 @@ func (ss *SecretsStoreImpl) ReEncryptDataKeys(
|
|||||||
ss.log.Warn(
|
ss.log.Warn(
|
||||||
"Could not find provider to re-encrypt data encryption key",
|
"Could not find provider to re-encrypt data encryption key",
|
||||||
"id", k.Id,
|
"id", k.Id,
|
||||||
"name", k.Name,
|
"label", k.Label,
|
||||||
"provider", k.Provider,
|
"provider", k.Provider,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
@@ -148,7 +148,7 @@ func (ss *SecretsStoreImpl) ReEncryptDataKeys(
|
|||||||
ss.log.Warn(
|
ss.log.Warn(
|
||||||
"Error while decrypting data encryption key to re-encrypt it",
|
"Error while decrypting data encryption key to re-encrypt it",
|
||||||
"id", k.Id,
|
"id", k.Id,
|
||||||
"name", k.Name,
|
"label", k.Label,
|
||||||
"provider", k.Provider,
|
"provider", k.Provider,
|
||||||
"err", err,
|
"err", err,
|
||||||
)
|
)
|
||||||
@@ -158,25 +158,25 @@ func (ss *SecretsStoreImpl) ReEncryptDataKeys(
|
|||||||
// Updating current data key by re-encrypting it with current provider.
|
// Updating current data key by re-encrypting it with current provider.
|
||||||
// Accessing the current provider within providers map should be safe.
|
// Accessing the current provider within providers map should be safe.
|
||||||
k.Provider = currProvider
|
k.Provider = currProvider
|
||||||
k.Name = secrets.KeyName(k.Scope, currProvider)
|
k.Label = secrets.KeyLabel(k.Scope, currProvider)
|
||||||
k.Updated = time.Now()
|
k.Updated = time.Now()
|
||||||
k.EncryptedData, err = providers[currProvider].Encrypt(ctx, decrypted)
|
k.EncryptedData, err = providers[currProvider].Encrypt(ctx, decrypted)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ss.log.Warn(
|
ss.log.Warn(
|
||||||
"Error while re-encrypting data encryption key",
|
"Error while re-encrypting data encryption key",
|
||||||
"id", k.Id,
|
"id", k.Id,
|
||||||
"name", k.Name,
|
"label", k.Label,
|
||||||
"provider", k.Provider,
|
"provider", k.Provider,
|
||||||
"err", err,
|
"err", err,
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := sess.Table(dataKeysTable).Where("id = ?", k.Id).Update(k); err != nil {
|
if _, err := sess.Table(dataKeysTable).Where("name = ?", k.Id).Update(k); err != nil {
|
||||||
ss.log.Warn(
|
ss.log.Warn(
|
||||||
"Error while re-encrypting data encryption key",
|
"Error while re-encrypting data encryption key",
|
||||||
"id", k.Id,
|
"id", k.Id,
|
||||||
"name", k.Name,
|
"label", k.Label,
|
||||||
"provider", k.Provider,
|
"provider", k.Provider,
|
||||||
"err", err,
|
"err", err,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ func (f FakeSecretsStore) GetDataKey(_ context.Context, id string) (*secrets.Dat
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f FakeSecretsStore) GetCurrentDataKey(_ context.Context, name string) (*secrets.DataKey, error) {
|
func (f FakeSecretsStore) GetCurrentDataKey(_ context.Context, label string) (*secrets.DataKey, error) {
|
||||||
for _, key := range f.store {
|
for _, key := range f.store {
|
||||||
if key.Name == name && key.Active {
|
if key.Label == label && key.Active {
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ var (
|
|||||||
|
|
||||||
type dataKeyCacheEntry struct {
|
type dataKeyCacheEntry struct {
|
||||||
id string
|
id string
|
||||||
name string
|
label string
|
||||||
dataKey []byte
|
dataKey []byte
|
||||||
|
active bool
|
||||||
expiration time.Time
|
expiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +27,14 @@ func (e dataKeyCacheEntry) expired() bool {
|
|||||||
type dataKeyCache struct {
|
type dataKeyCache struct {
|
||||||
mtx sync.RWMutex
|
mtx sync.RWMutex
|
||||||
byId map[string]*dataKeyCacheEntry
|
byId map[string]*dataKeyCacheEntry
|
||||||
byName map[string]*dataKeyCacheEntry
|
byLabel map[string]*dataKeyCacheEntry
|
||||||
cacheTTL time.Duration
|
cacheTTL time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDataKeyCache(ttl time.Duration) *dataKeyCache {
|
func newDataKeyCache(ttl time.Duration) *dataKeyCache {
|
||||||
return &dataKeyCache{
|
return &dataKeyCache{
|
||||||
byId: make(map[string]*dataKeyCacheEntry),
|
byId: make(map[string]*dataKeyCacheEntry),
|
||||||
byName: make(map[string]*dataKeyCacheEntry),
|
byLabel: make(map[string]*dataKeyCacheEntry),
|
||||||
cacheTTL: ttl,
|
cacheTTL: ttl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,15 +57,15 @@ func (c *dataKeyCache) getById(id string) (*dataKeyCacheEntry, bool) {
|
|||||||
return entry, true
|
return entry, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dataKeyCache) getByName(name string) (*dataKeyCacheEntry, bool) {
|
func (c *dataKeyCache) getByLabel(label string) (*dataKeyCacheEntry, bool) {
|
||||||
c.mtx.RLock()
|
c.mtx.RLock()
|
||||||
defer c.mtx.RUnlock()
|
defer c.mtx.RUnlock()
|
||||||
|
|
||||||
entry, exists := c.byName[name]
|
entry, exists := c.byLabel[label]
|
||||||
|
|
||||||
cacheReadsCounter.With(prometheus.Labels{
|
cacheReadsCounter.With(prometheus.Labels{
|
||||||
"hit": strconv.FormatBool(exists),
|
"hit": strconv.FormatBool(exists),
|
||||||
"method": "byName",
|
"method": "byLabel",
|
||||||
}).Inc()
|
}).Inc()
|
||||||
|
|
||||||
if !exists || entry.expired() {
|
if !exists || entry.expired() {
|
||||||
@@ -81,7 +82,7 @@ func (c *dataKeyCache) add(entry *dataKeyCacheEntry) {
|
|||||||
entry.expiration = now().Add(c.cacheTTL)
|
entry.expiration = now().Add(c.cacheTTL)
|
||||||
|
|
||||||
c.byId[entry.id] = entry
|
c.byId[entry.id] = entry
|
||||||
c.byName[entry.name] = entry
|
c.byLabel[entry.label] = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dataKeyCache) removeExpired() {
|
func (c *dataKeyCache) removeExpired() {
|
||||||
@@ -94,9 +95,9 @@ func (c *dataKeyCache) removeExpired() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, entry := range c.byName {
|
for label, entry := range c.byLabel {
|
||||||
if entry.expired() {
|
if entry.expired() {
|
||||||
delete(c.byName, name)
|
delete(c.byLabel, label)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +105,6 @@ func (c *dataKeyCache) removeExpired() {
|
|||||||
func (c *dataKeyCache) flush() {
|
func (c *dataKeyCache) flush() {
|
||||||
c.mtx.Lock()
|
c.mtx.Lock()
|
||||||
c.byId = make(map[string]*dataKeyCacheEntry)
|
c.byId = make(map[string]*dataKeyCacheEntry)
|
||||||
c.byName = make(map[string]*dataKeyCacheEntry)
|
c.byLabel = make(map[string]*dataKeyCacheEntry)
|
||||||
c.mtx.Unlock()
|
c.mtx.Unlock()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -146,11 +146,13 @@ func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byt
|
|||||||
|
|
||||||
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
// If encryption featuremgmt.FlagEnvelopeEncryption toggle is on, use envelope encryption
|
||||||
scope := opt()
|
scope := opt()
|
||||||
name := secrets.KeyName(scope, s.currentProviderID)
|
label := secrets.KeyLabel(scope, s.currentProviderID)
|
||||||
|
|
||||||
id, dataKey, err := s.currentDataKey(ctx, name, scope, sess)
|
var id string
|
||||||
|
var dataKey []byte
|
||||||
|
id, dataKey, err = s.currentDataKey(ctx, label, scope, sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error("Failed to get current data key", "error", err, "name", name)
|
s.log.Error("Failed to get current data key", "error", err, "label", label)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,21 +178,21 @@ func (s *SecretsService) EncryptWithDBSession(ctx context.Context, payload []byt
|
|||||||
// currentDataKey looks up for current data key in cache or database by name, and decrypts it.
|
// currentDataKey looks up for current data key in cache or database by name, and decrypts it.
|
||||||
// If there's no current data key in cache nor in database it generates a new random data key,
|
// If there's no current data key in cache nor in database it generates a new random data key,
|
||||||
// and stores it into both the in-memory cache and database (encrypted by the encryption provider).
|
// and stores it into both the in-memory cache and database (encrypted by the encryption provider).
|
||||||
func (s *SecretsService) currentDataKey(ctx context.Context, name string, scope string, sess *xorm.Session) (string, []byte, error) {
|
func (s *SecretsService) currentDataKey(ctx context.Context, label string, scope string, sess *xorm.Session) (string, []byte, error) {
|
||||||
// We want only one request fetching current data key at time to
|
// We want only one request fetching current data key at time to
|
||||||
// avoid the creation of multiple ones in case there's no one existing.
|
// avoid the creation of multiple ones in case there's no one existing.
|
||||||
s.mtx.Lock()
|
s.mtx.Lock()
|
||||||
defer s.mtx.Unlock()
|
defer s.mtx.Unlock()
|
||||||
|
|
||||||
// We try to fetch the data key, either from cache or database
|
// We try to fetch the data key, either from cache or database
|
||||||
id, dataKey, err := s.dataKeyByName(ctx, name)
|
id, dataKey, err := s.dataKeyByLabel(ctx, label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no existing data key was found, create a new one
|
// If no existing data key was found, create a new one
|
||||||
if dataKey == nil {
|
if dataKey == nil {
|
||||||
id, dataKey, err = s.newDataKey(ctx, name, scope, sess)
|
id, dataKey, err = s.newDataKey(ctx, label, scope, sess)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
@@ -199,16 +201,16 @@ func (s *SecretsService) currentDataKey(ctx context.Context, name string, scope
|
|||||||
return id, dataKey, nil
|
return id, dataKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dataKeyByName looks up for data key in cache.
|
// dataKeyByLabel looks up for data key in cache.
|
||||||
// Otherwise, it fetches it from database, decrypts it and caches it decrypted.
|
// Otherwise, it fetches it from database, decrypts it and caches it decrypted.
|
||||||
func (s *SecretsService) dataKeyByName(ctx context.Context, name string) (string, []byte, error) {
|
func (s *SecretsService) dataKeyByLabel(ctx context.Context, label string) (string, []byte, error) {
|
||||||
// 0. Get data key from in-memory cache.
|
// 0. Get data key from in-memory cache.
|
||||||
if entry, exists := s.dataKeyCache.getByName(name); exists {
|
if entry, exists := s.dataKeyCache.getByLabel(label); exists && entry.active {
|
||||||
return entry.id, entry.dataKey, nil
|
return entry.id, entry.dataKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Get data key from database.
|
// 1. Get data key from database.
|
||||||
dataKey, err := s.store.GetCurrentDataKey(ctx, name)
|
dataKey, err := s.store.GetCurrentDataKey(ctx, label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
if errors.Is(err, secrets.ErrDataKeyNotFound) {
|
||||||
return "", nil, nil
|
return "", nil, nil
|
||||||
@@ -229,13 +231,18 @@ func (s *SecretsService) dataKeyByName(ctx context.Context, name string) (string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Store the decrypted data key into the in-memory cache.
|
// 3. Store the decrypted data key into the in-memory cache.
|
||||||
s.dataKeyCache.add(&dataKeyCacheEntry{id: dataKey.Id, name: dataKey.Name, dataKey: decrypted})
|
s.dataKeyCache.add(&dataKeyCacheEntry{
|
||||||
|
id: dataKey.Id,
|
||||||
|
label: dataKey.Label,
|
||||||
|
dataKey: decrypted,
|
||||||
|
active: dataKey.Active,
|
||||||
|
})
|
||||||
|
|
||||||
return dataKey.Id, decrypted, nil
|
return dataKey.Id, decrypted, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDataKey creates a new random data key, encrypts it and stores it into the database and cache.
|
// newDataKey creates a new random data key, encrypts it and stores it into the database and cache.
|
||||||
func (s *SecretsService) newDataKey(ctx context.Context, name string, scope string, sess *xorm.Session) (string, []byte, error) {
|
func (s *SecretsService) newDataKey(ctx context.Context, label string, scope string, sess *xorm.Session) (string, []byte, error) {
|
||||||
// 1. Create new data key.
|
// 1. Create new data key.
|
||||||
dataKey, err := newRandomDataKey()
|
dataKey, err := newRandomDataKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -257,11 +264,11 @@ func (s *SecretsService) newDataKey(ctx context.Context, name string, scope stri
|
|||||||
// 3. Store its encrypted value into the DB.
|
// 3. Store its encrypted value into the DB.
|
||||||
id := util.GenerateShortUID()
|
id := util.GenerateShortUID()
|
||||||
dbDataKey := secrets.DataKey{
|
dbDataKey := secrets.DataKey{
|
||||||
Id: id,
|
|
||||||
Active: true,
|
Active: true,
|
||||||
Name: name,
|
Id: id,
|
||||||
Provider: s.currentProviderID,
|
Provider: s.currentProviderID,
|
||||||
EncryptedData: encrypted,
|
EncryptedData: encrypted,
|
||||||
|
Label: label,
|
||||||
Scope: scope,
|
Scope: scope,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +283,12 @@ func (s *SecretsService) newDataKey(ctx context.Context, name string, scope stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. Store the decrypted data key into the in-memory cache.
|
// 4. Store the decrypted data key into the in-memory cache.
|
||||||
s.dataKeyCache.add(&dataKeyCacheEntry{id: id, name: name, dataKey: dataKey})
|
s.dataKeyCache.add(&dataKeyCacheEntry{
|
||||||
|
id: id,
|
||||||
|
label: label,
|
||||||
|
dataKey: dataKey,
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
|
||||||
return id, dataKey, nil
|
return id, dataKey, nil
|
||||||
}
|
}
|
||||||
@@ -419,7 +431,12 @@ func (s *SecretsService) dataKeyById(ctx context.Context, id string) ([]byte, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. Store the decrypted data key into the in-memory cache.
|
// 3. Store the decrypted data key into the in-memory cache.
|
||||||
s.dataKeyCache.add(&dataKeyCacheEntry{id: id, name: dataKey.Name, dataKey: decrypted})
|
s.dataKeyCache.add(&dataKeyCacheEntry{
|
||||||
|
id: dataKey.Id,
|
||||||
|
label: dataKey.Label,
|
||||||
|
dataKey: decrypted,
|
||||||
|
active: dataKey.Active,
|
||||||
|
})
|
||||||
|
|
||||||
return decrypted, nil
|
return decrypted, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
|||||||
|
|
||||||
dataKey := &secrets.DataKey{
|
dataKey := &secrets.DataKey{
|
||||||
Id: util.GenerateShortUID(),
|
Id: util.GenerateShortUID(),
|
||||||
|
Label: "test1",
|
||||||
Active: true,
|
Active: true,
|
||||||
Name: "test1",
|
|
||||||
Provider: "test",
|
Provider: "test",
|
||||||
EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A},
|
EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A},
|
||||||
}
|
}
|
||||||
@@ -120,15 +120,15 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dataKey.EncryptedData, res.EncryptedData)
|
assert.Equal(t, dataKey.EncryptedData, res.EncryptedData)
|
||||||
assert.Equal(t, dataKey.Provider, res.Provider)
|
assert.Equal(t, dataKey.Provider, res.Provider)
|
||||||
assert.Equal(t, dataKey.Name, res.Name)
|
assert.Equal(t, dataKey.Label, res.Label)
|
||||||
assert.Equal(t, dataKey.Id, res.Id)
|
assert.Equal(t, dataKey.Id, res.Id)
|
||||||
assert.True(t, dataKey.Active)
|
assert.True(t, dataKey.Active)
|
||||||
|
|
||||||
current, err := store.GetCurrentDataKey(ctx, dataKey.Name)
|
current, err := store.GetCurrentDataKey(ctx, dataKey.Label)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, dataKey.EncryptedData, current.EncryptedData)
|
assert.Equal(t, dataKey.EncryptedData, current.EncryptedData)
|
||||||
assert.Equal(t, dataKey.Provider, current.Provider)
|
assert.Equal(t, dataKey.Provider, current.Provider)
|
||||||
assert.Equal(t, dataKey.Name, current.Name)
|
assert.Equal(t, dataKey.Label, current.Label)
|
||||||
assert.Equal(t, dataKey.Id, current.Id)
|
assert.Equal(t, dataKey.Id, current.Id)
|
||||||
assert.True(t, current.Active)
|
assert.True(t, current.Active)
|
||||||
})
|
})
|
||||||
@@ -137,7 +137,7 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
|||||||
k := &secrets.DataKey{
|
k := &secrets.DataKey{
|
||||||
Id: util.GenerateShortUID(),
|
Id: util.GenerateShortUID(),
|
||||||
Active: false,
|
Active: false,
|
||||||
Name: "test2",
|
Label: "test2",
|
||||||
Provider: "test",
|
Provider: "test",
|
||||||
EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A},
|
EncryptedData: []byte{0x62, 0xAF, 0xA1, 0x1A},
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ func TestSecretsService_DataKeys(t *testing.T) {
|
|||||||
err := store.CreateDataKey(ctx, k)
|
err := store.CreateDataKey(ctx, k)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
||||||
res, err := store.GetDataKey(ctx, k.Name)
|
res, err := store.GetDataKey(ctx, k.Id)
|
||||||
assert.Equal(t, secrets.ErrDataKeyNotFound, err)
|
assert.Equal(t, secrets.ErrDataKeyNotFound, err)
|
||||||
assert.Nil(t, res)
|
assert.Nil(t, res)
|
||||||
})
|
})
|
||||||
@@ -287,7 +287,7 @@ func TestSecretsService_Run(t *testing.T) {
|
|||||||
|
|
||||||
// Data encryption key cache should contain one element
|
// Data encryption key cache should contain one element
|
||||||
require.Len(t, svc.dataKeyCache.byId, 1)
|
require.Len(t, svc.dataKeyCache.byId, 1)
|
||||||
require.Len(t, svc.dataKeyCache.byName, 1)
|
require.Len(t, svc.dataKeyCache.byLabel, 1)
|
||||||
|
|
||||||
t.Cleanup(func() { now = time.Now })
|
t.Cleanup(func() { now = time.Now })
|
||||||
now = func() time.Time { return time.Now().Add(10 * time.Minute) }
|
now = func() time.Time { return time.Now().Add(10 * time.Minute) }
|
||||||
@@ -302,7 +302,7 @@ func TestSecretsService_Run(t *testing.T) {
|
|||||||
// the cleanup process should have happened,
|
// the cleanup process should have happened,
|
||||||
// therefore the cache should be empty.
|
// therefore the cache should be empty.
|
||||||
require.Len(t, svc.dataKeyCache.byId, 0)
|
require.Len(t, svc.dataKeyCache.byId, 0)
|
||||||
require.Len(t, svc.dataKeyCache.byName, 0)
|
require.Len(t, svc.dataKeyCache.byLabel, 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -337,12 +337,12 @@ func TestSecretsService_ReEncryptDataKeys(t *testing.T) {
|
|||||||
_, err := svc.Decrypt(ctx, ciphertext)
|
_, err := svc.Decrypt(ctx, ciphertext)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotEmpty(t, svc.dataKeyCache.byId)
|
require.NotEmpty(t, svc.dataKeyCache.byId)
|
||||||
require.NotEmpty(t, svc.dataKeyCache.byName)
|
require.NotEmpty(t, svc.dataKeyCache.byLabel)
|
||||||
|
|
||||||
err = svc.ReEncryptDataKeys(ctx)
|
err = svc.ReEncryptDataKeys(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Empty(t, svc.dataKeyCache.byId)
|
assert.Empty(t, svc.dataKeyCache.byId)
|
||||||
assert.Empty(t, svc.dataKeyCache.byName)
|
assert.Empty(t, svc.dataKeyCache.byLabel)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ type Service interface {
|
|||||||
// Store defines methods to interact with secrets storage
|
// Store defines methods to interact with secrets storage
|
||||||
type Store interface {
|
type Store interface {
|
||||||
GetDataKey(ctx context.Context, id string) (*DataKey, error)
|
GetDataKey(ctx context.Context, id string) (*DataKey, error)
|
||||||
GetCurrentDataKey(ctx context.Context, name string) (*DataKey, error)
|
GetCurrentDataKey(ctx context.Context, label string) (*DataKey, error)
|
||||||
GetAllDataKeys(ctx context.Context) ([]*DataKey, error)
|
GetAllDataKeys(ctx context.Context) ([]*DataKey, error)
|
||||||
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
|
||||||
@@ -61,7 +61,7 @@ func (id ProviderID) Kind() (string, error) {
|
|||||||
return parts[0], nil
|
return parts[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeyName(scope string, providerID ProviderID) string {
|
func KeyLabel(scope string, providerID ProviderID) string {
|
||||||
return fmt.Sprintf("%s/%s@%s", time.Now().Format("2006-01-02"), scope, providerID)
|
return fmt.Sprintf("%s/%s@%s", time.Now().Format("2006-01-02"), scope, providerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ var ErrDataKeyNotFound = errors.New("data key not found")
|
|||||||
|
|
||||||
type DataKey struct {
|
type DataKey struct {
|
||||||
Active bool
|
Active bool
|
||||||
Id string
|
Id string `xorm:"name"` // renaming the col in the db itself would break backward compatibility with 8.5.x
|
||||||
Name string
|
Label string
|
||||||
Scope string
|
Scope string
|
||||||
Provider ProviderID
|
Provider ProviderID
|
||||||
EncryptedData []byte
|
EncryptedData []byte
|
||||||
|
|||||||
@@ -61,4 +61,16 @@ func addSecretsMigration(mg *migrator.Migrator) {
|
|||||||
mg.AddMigration("copy data_keys id column values into name", migrator.NewRawSQLMigration(
|
mg.AddMigration("copy data_keys id column values into name", migrator.NewRawSQLMigration(
|
||||||
fmt.Sprintf("UPDATE %s SET %s = %s", dataKeysV1.Name, "name", "id"),
|
fmt.Sprintf("UPDATE %s SET %s = %s", dataKeysV1.Name, "name", "id"),
|
||||||
))
|
))
|
||||||
|
// ------- This is done for backward compatibility with versions > v8.3.x
|
||||||
|
mg.AddMigration("rename data_keys name column to label", migrator.NewRenameColumnMigration(
|
||||||
|
dataKeysV1, dataKeysV1.Columns[0], "label",
|
||||||
|
))
|
||||||
|
|
||||||
|
mg.AddMigration("rename data_keys id column back to name", migrator.NewRenameColumnMigration(
|
||||||
|
dataKeysV1,
|
||||||
|
&migrator.Column{Name: "id", Type: migrator.DB_NVarchar, Length: 100, IsPrimaryKey: true},
|
||||||
|
"name",
|
||||||
|
))
|
||||||
|
|
||||||
|
// --------------------
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user