diff --git a/internal/errors/error.go b/internal/errors/error.go index 72494c72da..d505210d80 100644 --- a/internal/errors/error.go +++ b/internal/errors/error.go @@ -74,7 +74,7 @@ func E(ctx context.Context, opt ...Option) error { Code: code, Op: opts.withOp, Wrapped: opts.withErrWrapped, - Msg: opts.withErrMsg, + Msg: fmt.Sprintf(opts.withErrMsg, opts.withErrMsgArgs...), } if opts.withoutEvent { return err diff --git a/internal/errors/option.go b/internal/errors/option.go index 89bc577ab6..1b70ba9f8a 100644 --- a/internal/errors/option.go +++ b/internal/errors/option.go @@ -17,6 +17,7 @@ type Options struct { withCode Code withErrWrapped error withErrMsg string + withErrMsgArgs []any withOp Op withoutEvent bool } @@ -34,10 +35,12 @@ func WithWrap(e error) Option { } // WithMsg provides an option to provide a message when creating a new -// error. -func WithMsg(msg string) Option { +// error. If args are provided, the the msg string is used as a fmt specifier +// for the arguments and the resulting string is used as the msg. +func WithMsg(msg string, args ...any) Option { return func(o *Options) { o.withErrMsg = msg + o.withErrMsgArgs = args } } diff --git a/internal/errors/option_test.go b/internal/errors/option_test.go index 3d7a0ac7eb..88912a408a 100644 --- a/internal/errors/option_test.go +++ b/internal/errors/option_test.go @@ -22,6 +22,13 @@ func Test_getOpts(t *testing.T) { opts = GetOpts(WithMsg("test msg")) testOpts.withErrMsg = "test msg" assert.Equal(opts, testOpts) + + opts = GetOpts(WithMsg("%s msg", "test")) + testOpts.withErrMsg = "%s msg" + testOpts.withErrMsgArgs = []any{"test"} + assert.Equal(opts, testOpts) + + assert.Equal("#1 test msg: unknown: error #0", E(context.Background(), WithMsg("#%d %s msg", 1, "test")).Error()) }) t.Run("WithWrap", func(t *testing.T) { assert := assert.New(t) diff --git a/internal/servers/options.go b/internal/servers/options.go index 9fb7f6f2e2..999782588e 100644 --- a/internal/servers/options.go +++ b/internal/servers/options.go @@ -3,6 +3,8 @@ package servers import ( "context" "time" + + "github.com/hashicorp/nodeenrollment/types" ) // getOpts - iterate the inbound Options and return a struct @@ -32,6 +34,7 @@ type options struct { withKeyId string withNonce []byte withNewIdFunc func(context.Context) (string, error) + withFetchNodeCredentialsRequest *types.FetchNodeCredentialsRequest } func getDefaultOptions() options { @@ -132,3 +135,11 @@ func WithNewIdFunc(fn func(context.Context) (string, error)) Option { o.withNewIdFunc = fn } } + +// WithFetchNodeCredentialsRequest allows an optional +// FetchNodeCredentialsRequest to be specified. +func WithFetchNodeCredentialsRequest(req *types.FetchNodeCredentialsRequest) Option { + return func(o *options) { + o.withFetchNodeCredentialsRequest = req + } +} diff --git a/internal/servers/repository_worker.go b/internal/servers/repository_worker.go index 787521afe5..3740af566c 100644 --- a/internal/servers/repository_worker.go +++ b/internal/servers/repository_worker.go @@ -8,8 +8,12 @@ import ( "github.com/hashicorp/boundary/internal/db" dbcommon "github.com/hashicorp/boundary/internal/db/common" "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/servers/store" "github.com/hashicorp/boundary/internal/types/scope" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/nodeenrollment" + "github.com/hashicorp/nodeenrollment/registration" ) // DeleteWorker will delete a worker from the repository. @@ -383,7 +387,8 @@ func (r *Repository) UpdateWorker(ctx context.Context, worker *Worker, version u // ReportedStatus and Tags are intentionally ignored when creating a worker (not // included). Currently, a worker can only be created in the global scope // -// Options supported: WithNewIdFunc (this option is likely only useful for tests) +// Options supported: WithFetchNodeCredentialsRequest and WithNewIdFunc (this +// option is likely only useful for tests) func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Option) (*Worker, error) { const op = "servers.CreateWorker" switch { @@ -409,25 +414,47 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate worker id")) } + var databaseWrapper wrapping.Wrapper + var workerAuthRepo *WorkerAuthRepositoryStorage + if opts.withFetchNodeCredentialsRequest != nil { + // used to encrypt the privKey within the NodeInformation + databaseWrapper, err = r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + } + var returnedWorker *Worker - _, err = r.writer.DoTx( + if _, err := r.writer.DoTx( ctx, db.StdRetryCnt, db.ExpBackoff{}, func(_ db.Reader, w db.Writer) error { returnedWorker = worker.clone() - err := w.Create( + if err := w.Create( ctx, returnedWorker, - ) - if err != nil { - return errors.Wrap(ctx, err, op) + ); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker")) + } + if opts.withFetchNodeCredentialsRequest != nil { + workerAuthRepo, err = NewRepositoryStorage(ctx, r.reader, r.writer, r.kms) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker auth repository")) + } + nodeInfo, err := registration.AuthorizeNode(ctx, workerAuthRepo, opts.withFetchNodeCredentialsRequest, nodeenrollment.WithSkipStorage(true)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to authorize node")) + } + if err := StoreNodeInformationTx(ctx, w, databaseWrapper, nodeInfo); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to store node information")) + } } return nil }, - ) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker")) + ); err != nil { + return nil, errors.Wrap(ctx, err, op) } return returnedWorker, nil }