diff --git a/go.mod b/go.mod index b27f877d0a..659ddea9ec 100644 --- a/go.mod +++ b/go.mod @@ -92,7 +92,7 @@ require github.com/hashicorp/go-dbw v0.0.0-20220725170111-b7cb3aa3d628 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/hashicorp/go-kms-wrapping/extras/kms/v2 v2.0.0-20220711120347-32232bae6803 - github.com/hashicorp/nodeenrollment v0.1.17-0.20220923113407-c95515d04322 + github.com/hashicorp/nodeenrollment v0.1.17 github.com/kelseyhightower/envconfig v1.4.0 golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e ) diff --git a/go.sum b/go.sum index 43bf69f5c8..77b9d4e564 100644 --- a/go.sum +++ b/go.sum @@ -743,8 +743,8 @@ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+l github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/nodeenrollment v0.1.17-0.20220923113407-c95515d04322 h1:rxhn6I2qDclJTCbcZ5GHbspFadA0+jZj7nOgpkTok5Y= -github.com/hashicorp/nodeenrollment v0.1.17-0.20220923113407-c95515d04322/go.mod h1:N5gYsm8mWiDfIw/j+1IQ6NBO1cWCmhPpvQ9GB1QUnsU= +github.com/hashicorp/nodeenrollment v0.1.17 h1:ZaGNugd3EOZIdJxgC5bUA1CbHZ/OKZcgXnquzoFql6E= +github.com/hashicorp/nodeenrollment v0.1.17/go.mod h1:N5gYsm8mWiDfIw/j+1IQ6NBO1cWCmhPpvQ9GB1QUnsU= github.com/hashicorp/vault/api v1.3.1 h1:pkDkcgTh47PRjY1NEFeofqR4W/HkNUi9qIakESO2aRM= github.com/hashicorp/vault/api v1.3.1/go.mod h1:QeJoWxMFt+MsuWcYhmwRLwKEXrjwAFFywzhptMsTIUw= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= diff --git a/internal/daemon/worker/auth_rotation.go b/internal/daemon/worker/auth_rotation.go index ad6e2c44c9..3f1ac929a4 100644 --- a/internal/daemon/worker/auth_rotation.go +++ b/internal/daemon/worker/auth_rotation.go @@ -143,6 +143,11 @@ func rotateWorkerAuth(ctx context.Context, w *Worker, currentNodeCreds *types.No return berrors.Wrap(ctx, err, op) } + err = newNodeCreds.SetPreviousEncryptionKey(currentNodeCreds) + if err != nil { + return berrors.Wrap(ctx, err, op) + } + // Get a signed request from the new credentials fetchReq, err := newNodeCreds.CreateFetchNodeCredentialsRequest(ctx, randReaderOpt) if err != nil { diff --git a/internal/daemon/worker/auth_rotation_test.go b/internal/daemon/worker/auth_rotation_test.go index 9f5c32c85f..cf3109075c 100644 --- a/internal/daemon/worker/auth_rotation_test.go +++ b/internal/daemon/worker/auth_rotation_test.go @@ -73,7 +73,7 @@ func TestRotationTicking(t *testing.T) { // Decode the proto into the request and create the worker req := new(types.FetchNodeCredentialsRequest) require.NoError(proto.Unmarshal(reqBytes, req)) - _, err = serversRepo.CreateWorker(c.Context(), &server.Worker{ + worker, err := serversRepo.CreateWorker(c.Context(), &server.Worker{ Worker: &store.Worker{ ScopeId: scope.Global.String(), }, @@ -89,16 +89,18 @@ func TestRotationTicking(t *testing.T) { require.NoError(err) currKey := currNodeCreds.CertificatePublicKeyPkix + priorKeyId, err := nodeenrollment.KeyIdFromPkix(currKey) + require.NoError(err) + // Now we wait and check that we see new values in the DB and different // creds on the worker after each rotation period for i := 2; i < 5; i++ { time.Sleep(rotationPeriod) - // Verify we see the expected number, since we aren't expiring any it - // should be equal to the number of times we did rotations + // Verify we see 2- after credentials have rotated, we should see current and previous auths, err = workerAuthRepo.List(c.Context(), (*types.NodeInformation)(nil)) require.NoError(err) - assert.Len(auths, i) + assert.Len(auths, 2) // Fetch creds and compare current key currNodeCreds, err := types.LoadNodeCredentials(w.Context(), w.Worker().WorkerAuthStorage, nodeenrollment.CurrentId, nodeenrollment.WithWrapper(w.Config().WorkerAuthStorageKms)) @@ -109,6 +111,23 @@ func TestRotationTicking(t *testing.T) { require.NoError(err) assert.Equal(currKeyId, w.Worker().WorkerAuthCurrentKeyId.Load()) + // Check that we've got the correct prior encryption key + previousKeyId, _, err := currNodeCreds.PreviousX25519EncryptionKey() + require.NoError(err) + assert.Equal(priorKeyId, previousKeyId) + + // Get workerAuthSet for this worker id and compare keys + workerAuthSet, err := workerAuthRepo.FindWorkerAuthByWorkerId(c.Context(), worker.PublicId) + require.NoError(err) + assert.NotNil(workerAuthSet) + assert.NotNil(workerAuthSet.Previous) + assert.NotNil(workerAuthSet.Current) + assert.Equal(workerAuthSet.Current.WorkerKeyIdentifier, currKeyId) + assert.Equal(workerAuthSet.Previous.WorkerKeyIdentifier, previousKeyId) + + // Save priorKeyId + priorKeyId = currKeyId + // Stop and start the client connections to ensure the new credentials // are valid; if not, we won't establish a new connection and rotation // will fail diff --git a/internal/db/schema/migrations/oss/postgres/34/03_worker_authentication.up.sql b/internal/db/schema/migrations/oss/postgres/34/03_worker_authentication.up.sql index 21d358a97f..e4f4e61706 100644 --- a/internal/db/schema/migrations/oss/postgres/34/03_worker_authentication.up.sql +++ b/internal/db/schema/migrations/oss/postgres/34/03_worker_authentication.up.sql @@ -103,6 +103,7 @@ create table worker_auth_authorized( comment on table worker_auth_authorized is 'worker_auth_authorized is a table where each row represents key and cert data associated with an authorized worker.'; +-- Trigger updated in 55/01_worker_auth_create_time.up.sql create trigger immutable_columns before update on worker_auth_authorized for each row execute procedure immutable_columns('worker_key_identifier', 'worker_id', 'worker_signing_pub_key', 'worker_encryption_pub_key', 'controller_encryption_priv_key', 'key_id', 'nonce'); diff --git a/internal/db/schema/migrations/oss/postgres/55/01_worker_auth_create_time.up.sql b/internal/db/schema/migrations/oss/postgres/55/01_worker_auth_create_time.up.sql new file mode 100644 index 0000000000..82a7fd1ed7 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/55/01_worker_auth_create_time.up.sql @@ -0,0 +1,138 @@ +begin; + +-- Add the create_time and update_time columns to the worker_auth_authorized +-- table and set the values for existing rows to -infinity since we only know +-- that the existing rows were created at some point in time prior to "now". +alter table worker_auth_authorized + add column create_time timestamp with time zone default '-infinity', + add column update_time timestamp with time zone default '-infinity' +; + +-- Now that values for the existing rows are set, change the create_time and +-- update_time columns to follow our standard pattern. +alter table worker_auth_authorized + alter column create_time drop default, + alter column update_time drop default, + alter column create_time type wt_timestamp, + alter column update_time type wt_timestamp +; + +create trigger default_create_time_column before insert on worker_auth_authorized + for each row execute procedure default_create_time(); + +create trigger update_time_column before update on worker_auth_authorized + for each row execute procedure update_time_column(); + +-- Add the worker_auth_authorized_state_enm table and insert enum the values +create table worker_auth_authorized_state_enm ( + state text primary key + constraint only_predefined_worker_auth_authorized_states_allowed + check ( + state in ('previous', 'current') + ) +); +comment on table credential_vault_token_status_enm is + 'worker_auth_authorized_state_enm is an enumeration table for the state column in the worker_auth_authorized table.'; + +insert into worker_auth_authorized_state_enm (state) +values + ('previous'), + ('current'); + +-- Add the state column to the worker_auth_authorized table and set the value +-- for existing rows to null. +alter table worker_auth_authorized + add column state text + constraint worker_auth_authorized_state_enm_fkey + references worker_auth_authorized_state_enm(state) + on delete restrict + on update cascade, + add constraint worker_auth_authorized_worker_id_state_uq + unique(worker_id, state) +; + +-- The worker_auth_authorized table may contain multiple rows for the same +-- worker_id. For each worker_id in the table, we need to pick one row and set +-- its state to 'current'. There is no way to be sure that we will pick the +-- correct row but we can use the xmin system column and the postgresql age() +-- function to make an educated guess. +-- +-- xmin is the transaction id (xid) of the transaction that inserted that +-- version of the row. The age(xid) function returns the age of the given xid +-- relative to the xid of the current transaction or the next-to-be-assigned +-- xid if the the age() is called outside of a transition. +-- +-- We use the xmin column and age function to select the row most recently +-- inserted or updated for each worker_id and set the state value for that row +-- to 'current'. +-- +-- References: +-- * xmin system column: https://www.postgresql.org/docs/14/ddl-system-columns.html +-- * age function: https://github.com/postgres/postgres/blob/REL_14_STABLE/src/backend/utils/adt/xid.c#L97-L111 +-- * xid data type: https://www.postgresql.org/docs/14/datatype-oid.html +with + last_inserted_rows (worker_id, row_age) as ( + select worker_id, min(age(xmin)) + from worker_auth_authorized + group by worker_id + ), + current_keys (worker_key_identifier) as ( + select worker_key_identifier + from worker_auth_authorized + where (worker_id, age(xmin)) in (select * from last_inserted_rows) + ) +update worker_auth_authorized +set state = 'current' +where worker_key_identifier in (select worker_key_identifier from current_keys); + +delete from worker_auth_authorized +where state is null; + +alter table worker_auth_authorized + alter column state set not null +; + +drop trigger immutable_columns on worker_auth_authorized; +-- Trigger updated from 34/03_worker_authentication.up.sql +create trigger immutable_columns before update on worker_auth_authorized + for each row execute function immutable_columns('worker_key_identifier', 'worker_id', 'worker_signing_pub_key', + 'worker_encryption_pub_key', 'controller_encryption_priv_key', 'key_id', 'nonce', 'create_time'); + +-- insert_worker_auth_authorized is a before insert trigger function for the +-- worker_auth_authorized table. The worker_auth_authorized table is a child +-- table of server_worker. A row contains a set encryption keys for a +-- server_worker that are unique to that worker. A server_worker can only have +-- two rows in the worker_auth_authorized table: one with a state of 'current' +-- and one with the state of 'previous'. +-- +-- A controller encrypts messages to a worker with the worker's 'current' keys. +-- A worker can encrypt messages to a controller with the worker's 'current' +-- or 'previous' keys. +-- +-- When a new row of keys is inserted for a worker, the new row of keys is +-- marked as 'current', the 'current' row of keys is changed to 'previous', +-- and the 'previous' row of keys is deleted. +create function insert_worker_auth_authorized() returns trigger +as $$ +begin + -- delete the worker's 'previous' row of keys + delete from worker_auth_authorized + where worker_id = new.worker_id + and state = 'previous'; + -- change the worker's 'current' row of keys to 'previous' + update worker_auth_authorized + set state = 'previous' + where worker_id = new.worker_id + and state = 'current'; + -- set the worker's new row of keys to 'current' + new.state = 'current'; + return new; +end; +$$ language plpgsql; +comment on function insert_worker_auth_authorized is + 'insert_worker_auth_authorized is a before insert trigger function for the worker_auth_authorized table.'; + +create trigger insert_worker_auth_authorized before insert on worker_auth_authorized + for each row execute function insert_worker_auth_authorized(); + +commit; \ No newline at end of file diff --git a/internal/proto/controller/storage/servers/store/v1/worker_auth.proto b/internal/proto/controller/storage/servers/store/v1/worker_auth.proto index abb728c738..1d0ce32a14 100644 --- a/internal/proto/controller/storage/servers/store/v1/worker_auth.proto +++ b/internal/proto/controller/storage/servers/store/v1/worker_auth.proto @@ -3,6 +3,8 @@ syntax = "proto3"; // Package store provides protobufs for storing types in the pki package. package controller.storage.servers.store.v1; +import "controller/storage/timestamp/v1/timestamp.proto"; + option go_package = "github.com/hashicorp/boundary/internal/server/store;store"; // WorkerAuth contains all fields related to an authorized Worker resource @@ -35,6 +37,19 @@ message WorkerAuth { // Nonce used by a worker in authenticating // @inject_tag: `gorm:"default:null"` bytes nonce = 70; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + storage.timestamp.v1.Timestamp create_time = 80; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + storage.timestamp.v1.Timestamp update_time = 90; + + // State of the worker auth record. + // The only valid value is either current or previous + // @inject_tag: `gorm:"not_null"` + string state = 100; } // WorkerCertBundle contains all fields related to a WorkerCertBundle resource diff --git a/internal/server/repository_workerauth.go b/internal/server/repository_workerauth.go index f2d218193d..52912446b0 100644 --- a/internal/server/repository_workerauth.go +++ b/internal/server/repository_workerauth.go @@ -369,6 +369,7 @@ func (r *WorkerAuthRepositoryStorage) Load(ctx context.Context, msg nodee.Messag // Node information is loaded in two parts: // * the workerAuth record // * its certificate bundles +// * the prior encryption key, if present func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, node *types.NodeInformation) error { const op = "server.(WorkerAuthRepositoryStorage).loadNodeInformation" if node == nil { @@ -418,6 +419,15 @@ 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 } @@ -507,6 +517,36 @@ func (r *WorkerAuthRepositoryStorage) findWorkerAuth(ctx context.Context, node * 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 { @@ -879,22 +919,56 @@ func decrypt(ctx context.Context, value []byte, wrapper wrapping.Wrapper) ([]byt return marshaledInfo, nil } -// FindWorkerAuthByWorkerId takes a workerId and returns the WorkerAuth record associated with that worker. -func (r *WorkerAuthRepositoryStorage) FindWorkerAuthByWorkerId(ctx context.Context, workerId string) (*WorkerAuth, error) { +// 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") } - worker := allocWorkerAuth() - worker.WorkerId = workerId - err := r.reader.SearchWhere(ctx, &worker, "worker_id = ?", []interface{}{workerId}) - if err != nil { - if errors.Is(err, dbw.ErrRecordNotFound) { - return nil, nil - } + 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) } - return worker, nil + 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)) + case workerAuthsFound == 1: + if workerAuths[0].State != currentWorkerAuthState { + return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, + fmt.Sprintf("expected sole worker auth record to be in current state, found %s", workerAuths[0].State)) + } else { + currentWorkerAuth = workerAuths[0] + } + case workerAuthsFound == 2: + currentStateFound := false + previousStateFound := false + for _, w := range workerAuths { + if w.State == currentWorkerAuthState { + currentStateFound = true + currentWorkerAuth = w + } else if w.State == previousWorkerAuthState { + previousStateFound = true + previousWorkerAuth = w + } + } + if !currentStateFound || !previousStateFound { + return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, fmt.Sprintf("worker auth records in invalid set of states")) + } + default: + return nil, errors.New(ctx, errors.NotSpecificIntegrity, op, + fmt.Sprintf("expected 2 or fewer worker auth records, found %d", workerAuthsFound)) + } + + workerAuthSet := &WorkerAuthSet{ + Previous: previousWorkerAuth, + Current: currentWorkerAuth, + } + + return workerAuthSet, nil } diff --git a/internal/server/repository_workerauth_test.go b/internal/server/repository_workerauth_test.go index 2fb82513b4..03b819d7e8 100644 --- a/internal/server/repository_workerauth_test.go +++ b/internal/server/repository_workerauth_test.go @@ -212,6 +212,11 @@ func TestStoreWorkerAuth(t *testing.T) { assert.Equal(nodeInfo.CertificatePublicKeyPkix, nodeLookup.CertificatePublicKeyPkix) assert.Equal(nodeInfo.State.AsMap(), nodeLookup.State.AsMap()) + // Validate that we can find the workerAuth set and key identifier + workerAuthSet, err := storage.FindWorkerAuthByWorkerId(ctx, worker.PublicId) + assert.NoError(err) + assert.Equal(workerAuthSet.Current.WorkerKeyIdentifier, keyId) + // Remove node err = storage.Remove(ctx, nodeLookup) require.NoError(err) diff --git a/internal/server/store/worker_auth.pb.go b/internal/server/store/worker_auth.pb.go index f85c318865..dac25fa911 100644 --- a/internal/server/store/worker_auth.pb.go +++ b/internal/server/store/worker_auth.pb.go @@ -9,6 +9,7 @@ package store import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -50,6 +51,16 @@ type WorkerAuth struct { // Nonce used by a worker in authenticating // @inject_tag: `gorm:"default:null"` Nonce []byte `protobuf:"bytes,70,opt,name=nonce,proto3" json:"nonce,omitempty" gorm:"default:null"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,80,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,90,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // State of the worker auth record. + // The only valid value is either current or previous + // @inject_tag: `gorm:"not_null"` + State string `protobuf:"bytes,100,opt,name=state,proto3" json:"state,omitempty" gorm:"not_null"` } func (x *WorkerAuth) Reset() { @@ -133,6 +144,27 @@ func (x *WorkerAuth) GetNonce() []byte { return nil } +func (x *WorkerAuth) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *WorkerAuth) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *WorkerAuth) GetState() string { + if x != nil { + return x.State + } + return "" +} + // WorkerCertBundle contains all fields related to a WorkerCertBundle resource type WorkerCertBundle struct { state protoimpl.MessageState @@ -304,55 +336,69 @@ var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{ 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x23, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x22, 0xbf, 0x02, 0x0a, - 0x0a, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x12, 0x32, 0x0a, 0x15, 0x77, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, - 0x66, 0x69, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, - 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, 0x16, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x70, - 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x77, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x4b, 0x65, - 0x79, 0x12, 0x39, 0x0a, 0x19, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x28, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x45, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x43, 0x0a, 0x1e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x32, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, - 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x76, 0x4b, 0x65, - 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, - 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x22, 0xa6, - 0x01, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x42, 0x75, 0x6e, - 0x64, 0x6c, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, 0x72, 0x6f, 0x6f, 0x74, 0x43, 0x65, - 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, - 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, - 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x5f, 0x62, - 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x65, 0x72, - 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x22, 0x57, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x41, - 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1b, - 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, - 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, 0x23, - 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x15, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, - 0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x28, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, - 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, - 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x72, 0x73, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xef, 0x03, + 0x0a, 0x0a, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x12, 0x32, 0x0a, 0x15, + 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, + 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x33, 0x0a, + 0x16, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, + 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x75, 0x62, 0x4b, + 0x65, 0x79, 0x12, 0x39, 0x0a, 0x19, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x75, 0x62, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x28, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x16, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x45, 0x6e, 0x63, + 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x43, 0x0a, + 0x1e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x65, 0x6e, 0x63, 0x72, + 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x72, 0x69, 0x76, 0x5f, 0x6b, 0x65, 0x79, 0x18, + 0x32, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x1b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x72, 0x69, 0x76, 0x4b, + 0x65, 0x79, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, + 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x50, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, + 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, + 0xa6, 0x01, 0x0a, 0x10, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x43, 0x65, 0x72, 0x74, 0x42, 0x75, + 0x6e, 0x64, 0x6c, 0x65, 0x12, 0x3d, 0x0a, 0x1b, 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, + 0x6b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x18, 0x72, 0x6f, 0x6f, 0x74, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x6b, 0x65, + 0x79, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4b, 0x65, 0x79, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x5f, + 0x62, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x65, + 0x72, 0x74, 0x42, 0x75, 0x6e, 0x64, 0x6c, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x22, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x41, 0x75, 0x74, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4c, 0x65, 0x64, + 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x1b, 0x0a, 0x09, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x17, 0x63, 0x72, 0x65, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, + 0x65, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x15, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x69, 0x6d, 0x65, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x23, 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x15, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x28, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x42, 0x3b, 0x5a, 0x39, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, + 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -372,13 +418,16 @@ var file_controller_storage_servers_store_v1_worker_auth_proto_goTypes = []inter (*WorkerAuth)(nil), // 0: controller.storage.servers.store.v1.WorkerAuth (*WorkerCertBundle)(nil), // 1: controller.storage.servers.store.v1.WorkerCertBundle (*WorkerAuthServerLedActivationToken)(nil), // 2: controller.storage.servers.store.v1.WorkerAuthServerLedActivationToken + (*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_servers_store_v1_worker_auth_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 3, // 0: controller.storage.servers.store.v1.WorkerAuth.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 3, // 1: controller.storage.servers.store.v1.WorkerAuth.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_controller_storage_servers_store_v1_worker_auth_proto_init() } diff --git a/internal/server/worker_auth.go b/internal/server/worker_auth.go index 7733af1a0f..c485b110a6 100644 --- a/internal/server/worker_auth.go +++ b/internal/server/worker_auth.go @@ -8,6 +8,11 @@ import ( "google.golang.org/protobuf/proto" ) +const ( + previousWorkerAuthState = "previous" + currentWorkerAuthState = "current" +) + // WorkerAuth contains all fields related to an authorized Worker resource // This includes worker public keys, the controller encryption key, // and certificate bundles issued by the Boundary CA @@ -16,6 +21,13 @@ type WorkerAuth struct { tableName string `gorm:"-"` } +// WorkerAuthSet is intended to store a set of WorkerAuth records +// This set represents the current and previous WorkerAuth records for a worker +type WorkerAuthSet struct { + Previous *WorkerAuth + Current *WorkerAuth +} + // WorkerKeys contain the signing and encryption keys for a WorkerAuth resource type WorkerKeys struct { workerSigningPubKey []byte diff --git a/internal/server/workerauth_store_test.go b/internal/server/workerauth_store_test.go index 4ea706ea2f..d85ce8e69c 100644 --- a/internal/server/workerauth_store_test.go +++ b/internal/server/workerauth_store_test.go @@ -415,6 +415,9 @@ func TestWorkerAuthStore(t *testing.T) { assert.Error(err) } else { assert.NoError(err) + // Update and create time are automatically set + tt.expectedWorkerAuth.CreateTime = wAuth.WorkerAuth.CreateTime + tt.expectedWorkerAuth.UpdateTime = wAuth.WorkerAuth.UpdateTime assert.Equal(tt.expectedWorkerAuth, wAuth.WorkerAuth) assert.Empty(cmp.Diff(tt.expectedWorkerAuth, wAuth, protocmp.Transform())) }