Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor keyring state store methods #23943

Merged
merged 1 commit into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion nomad/core_sched.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ func (c *CoreScheduler) rootKeyGC(eval *structs.Evaluation, now time.Time) error

// don't GC keys used to encrypt Variables or sign legacy non-expiring
// Workload Identities
inUse, err := c.snap.IsRootKeyMetaInUse(keyMeta.KeyID)
inUse, err := c.snap.IsRootKeyInUse(keyMeta.KeyID)
if err != nil {
return err
}
Expand Down
185 changes: 0 additions & 185 deletions nomad/state/state_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -7322,188 +7322,3 @@ func (s *StateSnapshot) DenormalizeAllocationDiffSlice(allocDiffs []*structs.All
func getPreemptedAllocDesiredDescription(preemptedByAllocID string) string {
return fmt.Sprintf("Preempted by alloc ID %v", preemptedByAllocID)
}

// UpsertRootKeyMeta saves root key meta or updates it in-place.
func (s *StateStore) UpsertRootKeyMeta(index uint64, rootKeyMeta *structs.RootKeyMeta, rekey bool) error {
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.IsActive() && rootKeyMeta.IsActive()
} else {
rootKeyMeta.CreateIndex = index
isRotation = rootKeyMeta.IsActive()
}
rootKeyMeta.ModifyIndex = index

if rekey && !isRotation {
return fmt.Errorf("cannot rekey without setting the new key active")
}

// 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)
modified := false

switch key.State {
case structs.RootKeyStateInactive:
if rekey {
key = key.MakeRekeying()
modified = true
}
case structs.RootKeyStateActive:
if rekey {
key = key.MakeRekeying()
} else {
key = key.MakeInactive()
}
modified = true
case structs.RootKeyStateRekeying, structs.RootKeyStateDeprecated:
// nothing to do
}

if modified {
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 {
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()

iter, err := txn.Get(TableRootKeyMeta, indexID)
if err != nil {
return nil, err
}

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.IsActive() {
return key, nil
}
}
return nil, nil
}

// IsRootKeyMetaInUse determines whether a key has been used to sign a workload
// identity for a live allocation or encrypt any variables
func (s *StateStore) IsRootKeyMetaInUse(keyID string) (bool, error) {
txn := s.db.ReadTxn()

iter, err := txn.Get(TableAllocs, indexSigningKey, keyID, true)
if err != nil {
return false, err
}
alloc := iter.Next()
if alloc != nil {
return true, nil
}

iter, err = txn.Get(TableVariables, indexKeyID, keyID)
if err != nil {
return false, err
}
variable := iter.Next()
if variable != nil {
return true, nil
}

return false, nil
}
196 changes: 196 additions & 0 deletions nomad/state/state_store_keyring.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// Copyright (c) HashiCorp, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bless your heart for moving this to a separate file 🙇‍♂️

// SPDX-License-Identifier: BUSL-1.1

package state

import (
"fmt"

memdb "github.com/hashicorp/go-memdb"
"github.com/hashicorp/nomad/nomad/structs"
)

// UpsertRootKeyMeta saves root key meta or updates it in-place.
func (s *StateStore) UpsertRootKeyMeta(index uint64, rootKeyMeta *structs.RootKeyMeta, rekey bool) error {
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.IsActive() && rootKeyMeta.IsActive()
} else {
rootKeyMeta.CreateIndex = index
isRotation = rootKeyMeta.IsActive()
}
rootKeyMeta.ModifyIndex = index

if rekey && !isRotation {
return fmt.Errorf("cannot rekey without setting the new key active")
}

// 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)
modified := false

switch key.State {
case structs.RootKeyStateInactive:
if rekey {
key = key.MakeRekeying()
modified = true
}
case structs.RootKeyStateActive:
if rekey {
key = key.MakeRekeying()
} else {
key = key.MakeInactive()
}
modified = true
case structs.RootKeyStateRekeying, structs.RootKeyStateDeprecated:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have to explicitly case this? why not just skip this line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It absolutely could be default: but when you've got a small enum like this I feel like it helps understandability to explicitly list them. If you add a state later it's more obvious that it's missing, rather than simply falling thru the default case and doing nothing.

// nothing to do
}

if modified {
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 {
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()

iter, err := txn.Get(TableRootKeyMeta, indexID)
if err != nil {
return nil, err
}

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.IsActive() {
return key, nil
}
}
return nil, nil
}

// IsRootKeyInUse determines whether a key has been used to sign a workload
// identity for a live allocation or encrypt any variables
func (s *StateStore) IsRootKeyInUse(keyID string) (bool, error) {
txn := s.db.ReadTxn()

iter, err := txn.Get(TableAllocs, indexSigningKey, keyID, true)
if err != nil {
return false, err
}
alloc := iter.Next()
if alloc != nil {
return true, nil
}

iter, err = txn.Get(TableVariables, indexKeyID, keyID)
if err != nil {
return false, err
}
variable := iter.Next()
if variable != nil {
return true, nil
}

return false, nil
}
Loading