diff --git a/internal/server/query.go b/internal/server/query.go index fa015bb28..a9ae233d9 100644 --- a/internal/server/query.go +++ b/internal/server/query.go @@ -23,4 +23,11 @@ const ( delete from worker_auth_ca_certificate where state = @state; ` + + getWorkerAuthsByWorkerKeyIdQuery = ` + with key_id_to_worker_id as ( + select worker_id from worker_auth_authorized where worker_key_identifier = @worker_key_identifier + ) + select * from worker_auth_authorized where worker_id in (select * from key_id_to_worker_id) + ` ) diff --git a/internal/server/repository_workerauth.go b/internal/server/repository_workerauth.go index 52912446b..5c1bf32e6 100644 --- a/internal/server/repository_workerauth.go +++ b/internal/server/repository_workerauth.go @@ -376,17 +376,49 @@ func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, n return errors.New(ctx, errors.InvalidParameter, op, "missing NodeInformation") } - authorizedWorker, err := r.findWorkerAuth(ctx, node) + query := getWorkerAuthsByWorkerKeyIdQuery + rows, err := r.reader.Query(ctx, query, []interface{}{sql.Named("worker_key_identifier", node.Id)}) if err != nil { return errors.Wrap(ctx, err, op) } - if authorizedWorker == nil { + defer rows.Close() + + var workerAuths []*WorkerAuth + for rows.Next() { + var s WorkerAuth + if err := r.reader.ScanRows(ctx, rows, &s); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed")) + } + workerAuths = append(workerAuths, &s) + } + + workerAuthorizedSet, err := r.validateWorkerAuths(ctx, workerAuths) + if err != nil { + if errors.IsNotFoundError(err) { + return nodee.ErrNotFound + } + return errors.Wrap(ctx, err, op) + } + + if workerAuthorizedSet == nil || workerAuthorizedSet.Current == nil { return nodee.ErrNotFound } - node.EncryptionPublicKeyBytes = authorizedWorker.WorkerEncryptionPubKey - node.CertificatePublicKeyPkix = authorizedWorker.WorkerSigningPubKey - node.RegistrationNonce = authorizedWorker.Nonce + if workerAuthorizedSet.Previous != nil { + priorKey := &types.EncryptionKey{ + KeyId: workerAuthorizedSet.Previous.WorkerKeyIdentifier, + PrivateKeyPkcs8: workerAuthorizedSet.Previous.ControllerEncryptionPrivKey, + PrivateKeyType: types.KEYTYPE_X25519, + PublicKeyPkix: workerAuthorizedSet.Previous.WorkerEncryptionPubKey, + PublicKeyType: types.KEYTYPE_X25519, + } + + node.PreviousEncryptionKey = priorKey + } + + node.EncryptionPublicKeyBytes = workerAuthorizedSet.Current.WorkerEncryptionPubKey + node.CertificatePublicKeyPkix = workerAuthorizedSet.Current.WorkerSigningPubKey + node.RegistrationNonce = workerAuthorizedSet.Current.Nonce // Default values are used for key types node.EncryptionPublicKeyType = types.KEYTYPE_X25519 @@ -394,16 +426,16 @@ func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, n node.ServerEncryptionPrivateKeyType = types.KEYTYPE_X25519 // Decrypt private key - databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(authorizedWorker.KeyId)) + databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(workerAuthorizedSet.Current.KeyId)) if err != nil { return errors.Wrap(ctx, err, op) } - node.ServerEncryptionPrivateKeyBytes, err = decrypt(ctx, authorizedWorker.ControllerEncryptionPrivKey, databaseWrapper) + node.ServerEncryptionPrivateKeyBytes, err = decrypt(ctx, workerAuthorizedSet.Current.ControllerEncryptionPrivKey, databaseWrapper) if err != nil { return errors.Wrap(ctx, err, op) } - workerIdInfo := workerAuthWorkerId{WorkerId: authorizedWorker.GetWorkerId()} + workerIdInfo := workerAuthWorkerId{WorkerId: workerAuthorizedSet.Current.GetWorkerId()} s := structs.New(workerIdInfo) s.TagName = "mapstructure" state, err := structpb.NewStruct(s.Map()) @@ -419,15 +451,6 @@ func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, n } node.CertificateBundles = certBundles - // Get prior encryption key, if available - priorKey, err := r.findPriorEncryptionKey(ctx, authorizedWorker.GetWorkerId()) - if !errors.IsNotFoundError(err) { - if err != nil { - return errors.Wrap(ctx, err, op) - } - node.PreviousEncryptionKey = priorKey - } - return nil } @@ -497,56 +520,6 @@ func (r *WorkerAuthRepositoryStorage) findCertBundles(ctx context.Context, worke return certBundle, nil } -func (r *WorkerAuthRepositoryStorage) findWorkerAuth(ctx context.Context, node *types.NodeInformation) (*WorkerAuth, error) { - const op = "server.(WorkerAuthRepositoryStorage).findWorkerAuth" - if node == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, "node is nil") - } - - worker := allocWorkerAuth() - worker.WorkerKeyIdentifier = node.Id - - err := r.reader.LookupById(ctx, worker) - if err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, nil - } - return nil, errors.Wrap(ctx, err, op) - } - - return worker, nil -} - -func (r *WorkerAuthRepositoryStorage) findPriorEncryptionKey(ctx context.Context, workerId string) (*types.EncryptionKey, error) { - const op = "server.(WorkerAuthRepositoryStorage).findPriorEncryptionKey" - if workerId == "" { - return nil, errors.New(ctx, errors.InvalidParameter, op, "empty workerId") - } - - workerAuthSet, err := r.FindWorkerAuthByWorkerId(ctx, workerId) - if err != nil { - if errors.IsNotFoundError(err) { - return nil, err - } - return nil, errors.Wrap(ctx, err, op) - } - - if workerAuthSet.Previous == nil { - return nil, nil - } - - // Create the EncryptionKey using the prior worker auth record - priorKey := &types.EncryptionKey{ - KeyId: workerAuthSet.Previous.WorkerKeyIdentifier, - PrivateKeyPkcs8: workerAuthSet.Previous.ControllerEncryptionPrivKey, - PrivateKeyType: types.KEYTYPE_X25519, - PublicKeyPkix: workerAuthSet.Previous.WorkerEncryptionPubKey, - PublicKeyType: types.KEYTYPE_X25519, - } - - return priorKey, nil -} - func (r *WorkerAuthRepositoryStorage) loadRootCertificates(ctx context.Context, cert *types.RootCertificates) error { const op = "server.(WorkerAuthRepositoryStorage).loadRootCertificates" if cert == nil { @@ -919,25 +892,16 @@ func decrypt(ctx context.Context, value []byte, wrapper wrapping.Wrapper) ([]byt return marshaledInfo, nil } -// FindWorkerAuthByWorkerId takes a workerId and returns the WorkerAuthSet for this worker. -func (r *WorkerAuthRepositoryStorage) FindWorkerAuthByWorkerId(ctx context.Context, workerId string) (*WorkerAuthSet, error) { - const op = "server.(WorkerAuthRepositoryStorage).FindWorkerAuthByWorkerId" - if len(workerId) == 0 { - return nil, errors.New(ctx, errors.InvalidParameter, op, "empty worker ID") - } +func (r *WorkerAuthRepositoryStorage) validateWorkerAuths(ctx context.Context, workerAuths []*WorkerAuth) (*WorkerAuthSet, error) { + const op = "server.(WorkerAuthRepositoryStorage).validateWorkerAuths" var previousWorkerAuth *WorkerAuth var currentWorkerAuth *WorkerAuth - var workerAuths []*WorkerAuth - if err := r.reader.SearchWhere(ctx, &workerAuths, "worker_id = ?", []interface{}{workerId}); err != nil { - return nil, errors.Wrap(ctx, err, op) - } - workerAuthsFound := len(workerAuths) switch { case workerAuthsFound == 0: - return nil, errors.New(ctx, errors.RecordNotFound, op, fmt.Sprintf("did not find worker auth records for worker %s", workerId)) + return nil, errors.New(ctx, errors.RecordNotFound, op, "did not find worker auth records for worker") case workerAuthsFound == 1: if workerAuths[0].State != currentWorkerAuthState { return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, @@ -972,3 +936,18 @@ func (r *WorkerAuthRepositoryStorage) FindWorkerAuthByWorkerId(ctx context.Conte return workerAuthSet, nil } + +// FindWorkerAuthByWorkerId takes a workerId and returns the WorkerAuthSet for this worker. +func (r *WorkerAuthRepositoryStorage) FindWorkerAuthByWorkerId(ctx context.Context, workerId string) (*WorkerAuthSet, error) { + const op = "server.(WorkerAuthRepositoryStorage).FindWorkerAuthByWorkerId" + if len(workerId) == 0 { + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty worker ID") + } + + var workerAuths []*WorkerAuth + if err := r.reader.SearchWhere(ctx, &workerAuths, "worker_id = ?", []interface{}{workerId}); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + return r.validateWorkerAuths(ctx, workerAuths) +} diff --git a/internal/server/repository_workerauth_test.go b/internal/server/repository_workerauth_test.go index 03b819d7e..e3f1f0bc4 100644 --- a/internal/server/repository_workerauth_test.go +++ b/internal/server/repository_workerauth_test.go @@ -17,6 +17,7 @@ import ( "github.com/hashicorp/boundary/internal/types/scope" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/nodeenrollment" + nodee "github.com/hashicorp/nodeenrollment" "github.com/hashicorp/nodeenrollment/registration" "github.com/hashicorp/nodeenrollment/rotation" "github.com/hashicorp/nodeenrollment/storage/file" @@ -190,6 +191,13 @@ func TestStoreWorkerAuth(t *testing.T) { storage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache) require.NoError(err) + // Look for node info and expect to find nothing + nodeLookup := &types.NodeInformation{ + Id: keyId, + } + err = storage.Load(ctx, nodeLookup) + assert.Equal(err, nodee.ErrNotFound) + // The AuthorizeNode request will result in a WorkerAuth record being stored _, err = registration.AuthorizeNode(ctx, storage, fetchReq, nodeenrollment.WithState(state)) require.NoError(err) @@ -201,9 +209,6 @@ func TestStoreWorkerAuth(t *testing.T) { assert.Len(nodeInfos, 1) // Validate the stored fields match those from the worker - nodeLookup := &types.NodeInformation{ - Id: keyId, - } err = storage.Load(ctx, nodeLookup) require.NoError(err) assert.NotEmpty(nodeLookup) @@ -211,6 +216,7 @@ func TestStoreWorkerAuth(t *testing.T) { assert.Equal(nodeInfo.RegistrationNonce, nodeLookup.RegistrationNonce) assert.Equal(nodeInfo.CertificatePublicKeyPkix, nodeLookup.CertificatePublicKeyPkix) assert.Equal(nodeInfo.State.AsMap(), nodeLookup.State.AsMap()) + assert.Empty(nodeInfo.PreviousEncryptionKey) // Validate that we can find the workerAuth set and key identifier workerAuthSet, err := storage.FindWorkerAuthByWorkerId(ctx, worker.PublicId)