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:
Tim Gross
2022-05-19 14:19:56 -04:00
parent 7c40638255
commit 233fc63168
3 changed files with 210 additions and 3 deletions

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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