mirror of
https://github.com/kemko/nomad.git
synced 2026-01-07 10:55:42 +03:00
keyring state store operations (#13016)
Implement the basic upsert, list, and delete operations for `RootKeyMeta` needed by the Keyring RPCs. This changeset also implements two convenience methods `RootKeyMetaByID` and `GetActiveRootKeyMeta` which are useful for testing but also will be needed to implement the rest of the RPCs.
This commit is contained in:
@@ -6664,14 +6664,92 @@ func (s *StateStore) SecureVariablesQuotas(ws memdb.WatchSet) (memdb.ResultItera
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// UpsertRootKeyMeta saves root key meta or updates it in-place.
|
||||
func (s *StateStore) UpsertRootKeyMeta(index uint64, rootKeyMeta *structs.RootKeyMeta) error {
|
||||
return nil
|
||||
txn := s.db.WriteTxn(index)
|
||||
defer txn.Abort()
|
||||
|
||||
// get any existing key for updating
|
||||
raw, err := txn.First(TableRootKeyMeta, indexID, rootKeyMeta.KeyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("root key metadata lookup failed: %v", err)
|
||||
}
|
||||
|
||||
isRotation := false
|
||||
|
||||
if raw != nil {
|
||||
existing := raw.(*structs.RootKeyMeta)
|
||||
rootKeyMeta.CreateIndex = existing.CreateIndex
|
||||
rootKeyMeta.CreateTime = existing.CreateTime
|
||||
isRotation = !existing.Active && rootKeyMeta.Active
|
||||
} else {
|
||||
rootKeyMeta.CreateIndex = index
|
||||
rootKeyMeta.CreateTime = time.Now()
|
||||
isRotation = rootKeyMeta.Active
|
||||
}
|
||||
rootKeyMeta.ModifyIndex = index
|
||||
|
||||
// if the upsert is for a newly-active key, we need to set all the
|
||||
// other keys as inactive in the same transaction.
|
||||
if isRotation {
|
||||
iter, err := txn.Get(TableRootKeyMeta, indexID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
raw := iter.Next()
|
||||
if raw == nil {
|
||||
break
|
||||
}
|
||||
key := raw.(*structs.RootKeyMeta)
|
||||
if key.Active {
|
||||
key.Active = false
|
||||
key.ModifyIndex = index
|
||||
if err := txn.Insert(TableRootKeyMeta, key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := txn.Insert(TableRootKeyMeta, rootKeyMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update the indexes table
|
||||
if err := txn.Insert("index", &IndexEntry{TableRootKeyMeta, index}); err != nil {
|
||||
return fmt.Errorf("index update failed: %v", err)
|
||||
}
|
||||
return txn.Commit()
|
||||
}
|
||||
|
||||
// DeleteRootKeyMeta deletes a single root key, or returns an error if
|
||||
// it doesn't exist.
|
||||
func (s *StateStore) DeleteRootKeyMeta(index uint64, keyID string) error {
|
||||
return nil
|
||||
txn := s.db.WriteTxn(index)
|
||||
defer txn.Abort()
|
||||
|
||||
// find the old key
|
||||
existing, err := txn.First(TableRootKeyMeta, indexID, keyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("root key metadata lookup failed: %v", err)
|
||||
}
|
||||
if existing == nil {
|
||||
return fmt.Errorf("root key metadata not found")
|
||||
}
|
||||
if err := txn.Delete(TableRootKeyMeta, existing); err != nil {
|
||||
return fmt.Errorf("root key metadata delete failed: %v", err)
|
||||
}
|
||||
|
||||
// update the indexes table
|
||||
if err := txn.Insert("index", &IndexEntry{TableRootKeyMeta, index}); err != nil {
|
||||
return fmt.Errorf("index update failed: %v", err)
|
||||
}
|
||||
|
||||
return txn.Commit()
|
||||
}
|
||||
|
||||
// RootKeyMetas returns an iterator over all root key metadata
|
||||
func (s *StateStore) RootKeyMetas(ws memdb.WatchSet) (memdb.ResultIterator, error) {
|
||||
txn := s.db.ReadTxn()
|
||||
|
||||
@@ -6683,3 +6761,42 @@ func (s *StateStore) RootKeyMetas(ws memdb.WatchSet) (memdb.ResultIterator, erro
|
||||
ws.Add(iter.WatchCh())
|
||||
return iter, nil
|
||||
}
|
||||
|
||||
// RootKeyMetaByID returns a specific root key meta
|
||||
func (s *StateStore) RootKeyMetaByID(ws memdb.WatchSet, id string) (*structs.RootKeyMeta, error) {
|
||||
txn := s.db.ReadTxn()
|
||||
|
||||
watchCh, raw, err := txn.FirstWatch(TableRootKeyMeta, indexID, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("root key metadata lookup failed: %v", err)
|
||||
}
|
||||
ws.Add(watchCh)
|
||||
|
||||
if raw != nil {
|
||||
return raw.(*structs.RootKeyMeta), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetActiveRootKeyMeta returns the metadata for the currently active root key
|
||||
func (s *StateStore) GetActiveRootKeyMeta(ws memdb.WatchSet) (*structs.RootKeyMeta, error) {
|
||||
txn := s.db.ReadTxn()
|
||||
|
||||
iter, err := txn.Get(TableRootKeyMeta, indexID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ws.Add(iter.WatchCh())
|
||||
|
||||
for {
|
||||
raw := iter.Next()
|
||||
if raw == nil {
|
||||
break
|
||||
}
|
||||
key := raw.(*structs.RootKeyMeta)
|
||||
if key.Active {
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -9908,6 +9908,75 @@ func TestStateStore_UpsertScalingEvent_LimitAndOrder(t *testing.T) {
|
||||
require.Equal(expectedEvents, actualEvents)
|
||||
}
|
||||
|
||||
func TestStateStore_RootKeyMetaData_CRUD(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
store := testStateStore(t)
|
||||
index, err := store.LatestIndex()
|
||||
require.NoError(t, err)
|
||||
|
||||
// create 3 default keys, one of which is active
|
||||
keyIDs := []string{}
|
||||
for i := 0; i < 3; i++ {
|
||||
key := structs.NewRootKeyMeta()
|
||||
keyIDs = append(keyIDs, key.KeyID)
|
||||
if i == 0 {
|
||||
key.Active = true
|
||||
}
|
||||
index++
|
||||
require.NoError(t, store.UpsertRootKeyMeta(index, key))
|
||||
}
|
||||
|
||||
// retrieve the active key
|
||||
activeKey, err := store.GetActiveRootKeyMeta(nil)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, activeKey)
|
||||
|
||||
// update an inactive key to active and verify the rotation
|
||||
inactiveKey, err := store.RootKeyMetaByID(nil, keyIDs[1])
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, inactiveKey)
|
||||
oldCreateIndex := inactiveKey.CreateIndex
|
||||
newlyActiveKey := inactiveKey.Copy()
|
||||
newlyActiveKey.Active = true
|
||||
index++
|
||||
require.NoError(t, store.UpsertRootKeyMeta(index, newlyActiveKey))
|
||||
|
||||
iter, err := store.RootKeyMetas(nil)
|
||||
require.NoError(t, err)
|
||||
for {
|
||||
raw := iter.Next()
|
||||
if raw == nil {
|
||||
break
|
||||
}
|
||||
key := raw.(*structs.RootKeyMeta)
|
||||
if key.KeyID == newlyActiveKey.KeyID {
|
||||
require.True(t, key.Active, "expected updated key to be active")
|
||||
require.Equal(t, oldCreateIndex, key.CreateIndex)
|
||||
} else {
|
||||
require.False(t, key.Active, "expected other keys to be inactive")
|
||||
}
|
||||
}
|
||||
|
||||
// delete the active key and verify it's been deleted
|
||||
index++
|
||||
require.NoError(t, store.DeleteRootKeyMeta(index, keyIDs[1]))
|
||||
|
||||
iter, err = store.RootKeyMetas(nil)
|
||||
require.NoError(t, err)
|
||||
var found int
|
||||
for {
|
||||
raw := iter.Next()
|
||||
if raw == nil {
|
||||
break
|
||||
}
|
||||
key := raw.(*structs.RootKeyMeta)
|
||||
require.NotEqual(t, keyIDs[1], key.KeyID)
|
||||
require.False(t, key.Active, "expected remaining keys to be inactive")
|
||||
found++
|
||||
}
|
||||
require.Equal(t, 2, found, "expected only 2 keys remaining")
|
||||
}
|
||||
|
||||
func TestStateStore_Abandon(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
package structs
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/nomad/helper/uuid"
|
||||
)
|
||||
|
||||
// SecureVariable is the metadata envelope for a Secure Variable
|
||||
type SecureVariable struct {
|
||||
@@ -151,6 +155,23 @@ type RootKeyMeta struct {
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// NewRootKeyMeta returns a new RootKeyMeta with default values
|
||||
func NewRootKeyMeta() *RootKeyMeta {
|
||||
return &RootKeyMeta{
|
||||
KeyID: uuid.Generate(),
|
||||
Algorithm: EncryptionAlgorithmXChaCha20,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
func (rkm *RootKeyMeta) Copy() *RootKeyMeta {
|
||||
if rkm == nil {
|
||||
return nil
|
||||
}
|
||||
out := *rkm
|
||||
return &out
|
||||
}
|
||||
|
||||
// EncryptionAlgorithm chooses which algorithm is used for
|
||||
// encrypting / decrypting entries with this key
|
||||
type EncryptionAlgorithm string
|
||||
|
||||
Reference in New Issue
Block a user