diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d043447b..ff49ec7be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,49 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. ## Next +### New and Improved + +* Controller-led worker authorization: This is a second authorization option for + the workers using PKI-based authentication that was introduced in Boundary + 0.10. In 0.10, the only mode available was "worker-led", in which a worker + generates an authorization request which can be submitted to a controller to + authorize the worker. With this new controller-led flow, a worker can be + created via the controller API first and return a one-time-use authorization + token. This token can then be made available to the worker at startup time via + its configuration file, env var, or a file with the value. If the worker is + not authorized and this token is provided, it will use the token to authorize + itself to the controller and set up PKI-based authentication. + ([PR](https://github.com/hashicorp/boundary/pull/2413)) +* Initial upstreams reloading on `SIGHUP`: Workers will now re-read the + `initial_upstreams` value from the configuration file when given a SIGHUP. + This allows a worker to reconnect to controllers if the full set of + controllers has been changed over at the same time, without having to restart + the worker. ([PR](https://github.com/hashicorp/boundary/pull/2417)) + ### Bug Fixes -* Sessions: Fix an issue where sessions could not have more than one connection -([Issue](https://github.com/hashicorp/boundary/issues/2362)), ([PR](https://github.com/hashicorp/boundary/pull/2369)) + +* aws host catalog: Fix an issue where the request to list hosts could timeout + on a large number of hosts + ([Issue](https://github.com/hashicorp/boundary/issues/2224), + [PR](https://github.com/hashicorp/boundary-plugin-host-aws/pull/17)) +* aws host catalog: Fix an issue where filters could become unreadable in the UI + if only one filter was created and was set by the CLI or directly via the API + ([PR1](https://github.com/hashicorp/boundary/pull/2376), + [PR2](https://github.com/hashicorp/boundary-plugin-host-aws/pull/16)) +* azure host catalog: Fix hosts not being found depending on the exact filter + used because different filters return values with different casing + ([PR](https://github.com/hashicorp/boundary-plugin-host-azure/pull/8)) +* sessions: Fix an issue where sessions could not have more than one connection + ([Issue](https://github.com/hashicorp/boundary/issues/2362)), + ([PR](https://github.com/hashicorp/boundary/pull/2369)) +* workers: Fix repeating error in logs when connected to HCP Boundary about an + unimplemented HcpbWorkers call + ([PR](https://github.com/hashicorp/boundary/pull/2361)) +* workers: Fix a panic that could occur when `workers:create:worker-led` (e.g. + via `boundary workers create worker-led`) was given an invalid token + ([PR](https://github.com/hashicorp/boundary/pull/2388)) +* workers: Add the ability to set API-based worker tags via the CLI + ([PR](https://github.com/hashicorp/boundary/pull/2266)) ## 0.10.3 (2022/08/30) @@ -33,15 +73,15 @@ Canonical reference for changes, improvements, and bugfixes for Boundary. credential libraries or static credentials. ([Issue](https://github.com/hashicorp/boundary/issues/2349)), ([PR](https://github.com/hashicorp/boundary/pull/2351)). -* Managed Groups: Fix an issue where the `filter` field is not sent by +* managed groups: Fix an issue where the `filter` field is not sent by admin UI ([PR](https://github.com/hashicorp/boundary-ui/pull/1238)). -* Host Sets: Fix an issue causing host sets to not display in UI when using the aws plugin +* host sets: Fix an issue causing host sets to not display in UI when using the aws plugin ([PR](https://github.com/hashicorp/boundary-ui/pull/1251)) -* Plugins: Fixes regression from 0.9.0 causing a failure to start when using +* plugins: Fixes regression from 0.9.0 causing a failure to start when using multiple KMS blocks of the same type ([PR1](https://github.com/hashicorp/go-secure-stdlib/pull/43), [PR2](https://github.com/hashicorp/boundary/pull/2346)) -* CLI: Fixed errors related to URL detection when passing in `-attr` or +* cli: Fixed errors related to URL detection when passing in `-attr` or `-secret` values that contained colons ([PR](https://github.com/hashicorp/boundary/pull/2353)) diff --git a/api/workers/worker.gen.go b/api/workers/worker.gen.go index 898410b64..ba7bf15ad 100644 --- a/api/workers/worker.gen.go +++ b/api/workers/worker.gen.go @@ -13,23 +13,24 @@ import ( ) type Worker struct { - Id string `json:"id,omitempty"` - ScopeId string `json:"scope_id,omitempty"` - Scope *scopes.ScopeInfo `json:"scope,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - CreatedTime time.Time `json:"created_time,omitempty"` - UpdatedTime time.Time `json:"updated_time,omitempty"` - Version uint32 `json:"version,omitempty"` - Address string `json:"address,omitempty"` - CanonicalTags map[string][]string `json:"canonical_tags,omitempty"` - ConfigTags map[string][]string `json:"config_tags,omitempty"` - LastStatusTime time.Time `json:"last_status_time,omitempty"` - WorkerGeneratedAuthToken string `json:"worker_generated_auth_token,omitempty"` - ActiveConnectionCount uint32 `json:"active_connection_count,omitempty"` - Type string `json:"type,omitempty"` - ApiTags map[string][]string `json:"api_tags,omitempty"` - AuthorizedActions []string `json:"authorized_actions,omitempty"` + Id string `json:"id,omitempty"` + ScopeId string `json:"scope_id,omitempty"` + Scope *scopes.ScopeInfo `json:"scope,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + UpdatedTime time.Time `json:"updated_time,omitempty"` + Version uint32 `json:"version,omitempty"` + Address string `json:"address,omitempty"` + CanonicalTags map[string][]string `json:"canonical_tags,omitempty"` + ConfigTags map[string][]string `json:"config_tags,omitempty"` + LastStatusTime time.Time `json:"last_status_time,omitempty"` + WorkerGeneratedAuthToken string `json:"worker_generated_auth_token,omitempty"` + ControllerGeneratedActivationToken string `json:"controller_generated_activation_token,omitempty"` + ActiveConnectionCount uint32 `json:"active_connection_count,omitempty"` + Type string `json:"type,omitempty"` + ApiTags map[string][]string `json:"api_tags,omitempty"` + AuthorizedActions []string `json:"authorized_actions,omitempty"` response *api.Response } @@ -143,6 +144,50 @@ func (c *Client) CreateWorkerLed(ctx context.Context, workerGeneratedAuthToken s return target, nil } +func (c *Client) CreateControllerLed(ctx context.Context, scopeId string, opt ...Option) (*WorkerCreateResult, error) { + if scopeId == "" { + return nil, fmt.Errorf("empty scopeId value passed into CreateControllerLed request") + } + + opts, apiOpts := getOpts(opt...) + + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts.postMap["scope_id"] = scopeId + + req, err := c.client.NewRequest(ctx, "POST", "workers:create:controller-led", opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating CreateControllerLed request: %w", err) + } + + if len(opts.queryMap) > 0 { + q := url.Values{} + for k, v := range opts.queryMap { + q.Add(k, v) + } + req.URL.RawQuery = q.Encode() + } + + resp, err := c.client.Do(req) + if err != nil { + return nil, fmt.Errorf("error performing client request during CreateControllerLed call: %w", err) + } + + target := new(WorkerCreateResult) + target.Item = new(Worker) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding CreateControllerLed response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*WorkerReadResult, error) { if id == "" { return nil, fmt.Errorf("empty id value passed into Read request") diff --git a/globals/fields.go b/globals/fields.go index 815cc37d6..af5a47556 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -84,4 +84,5 @@ const ( WorkerGeneratedAuthTokenField = "worker_generated_auth_token" WorkerProvidedConfigurationField = "worker_provided_configuration" ActiveConnectionCountField = "active_connection_count" + ControllerGeneratedActivationToken = "controller_generated_activation_token" ) diff --git a/go.mod b/go.mod index 46d95885c..e573b26af 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.13 + github.com/hashicorp/nodeenrollment v0.1.16 ) require ( diff --git a/go.sum b/go.sum index ae151460b..0c296c092 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.13 h1:h830EbaWqt1wyRi3DERDtUHLW3tXl4cLfbVkPeD5Twc= -github.com/hashicorp/nodeenrollment v0.1.13/go.mod h1:LIPKi+g0g/vl3xhpbzugCalHSxX1PMeqnatkAsxRgyM= +github.com/hashicorp/nodeenrollment v0.1.16 h1:TZ+lN7uvkW733OZE25Wo1FgkC0zwcQo1eo3NtEnHLkw= +github.com/hashicorp/nodeenrollment v0.1.16/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/api/genapi/input.go b/internal/api/genapi/input.go index b7c402854..f33f0cce3 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -95,9 +95,6 @@ type structInfo struct { // useful to avoid collisions nameOverride string - // extraRequiredParams allows adding extra required parameters to templates - extraRequiredParams []requiredParam - // recursiveListing indicates that the collection supports recursion when // listing recursiveListing bool @@ -308,20 +305,32 @@ var inputStructs = []*structInfo{ outFile: "authmethods/authmethods.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, listTemplate, }, - pluralResourceName: "auth-methods", - extraRequiredParams: []requiredParam{ - { - Name: "resourceType", - Typ: "string", - PostType: "type", - }, - }, + pluralResourceName: "auth-methods", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -422,21 +431,33 @@ var inputStructs = []*structInfo{ outFile: "credentialstores/credential_store.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, listTemplate, }, - pluralResourceName: "credential-stores", - parentTypeName: "scope", - extraRequiredParams: []requiredParam{ - { - Name: "resourceType", - Typ: "string", - PostType: "type", - }, - }, + pluralResourceName: "credential-stores", + parentTypeName: "scope", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -525,19 +546,31 @@ var inputStructs = []*structInfo{ outFile: "credentials/credential.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, listTemplate, }, - extraRequiredParams: []requiredParam{ - { - Name: "resourceType", - Typ: "string", - PostType: "type", - }, - }, pluralResourceName: "credentials", parentTypeName: "credential-store", versionEnabled: true, @@ -550,7 +583,26 @@ var inputStructs = []*structInfo{ outFile: "hostcatalogs/host_catalog.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, @@ -571,14 +623,7 @@ var inputStructs = []*structInfo{ SkipDefault: true, }, }, - pluralResourceName: "host-catalogs", - extraRequiredParams: []requiredParam{ - { - Name: "resourceType", - Typ: "string", - PostType: "type", - }, - }, + pluralResourceName: "host-catalogs", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -689,7 +734,26 @@ var inputStructs = []*structInfo{ outFile: "targets/target.gen.go", templates: []*template.Template{ clientTemplate, - commonCreateTemplate, + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "Create" + }, + "apiAction": func() string { + return "" + }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "resourceType", + Typ: "string", + PostType: "type", + }, + } + }, + }, + ).Parse(createTemplateStr)), readTemplate, updateTemplate, deleteTemplate, @@ -742,14 +806,7 @@ var inputStructs = []*structInfo{ FieldType: "[]string", }, }, - versionEnabled: true, - extraRequiredParams: []requiredParam{ - { - Name: "resourceType", - Typ: "string", - PostType: "type", - }, - }, + versionEnabled: true, createResponseTypes: true, recursiveListing: true, }, @@ -796,6 +853,29 @@ var inputStructs = []*structInfo{ "apiAction": func() string { return ":create:worker-led" }, + "extraRequiredParams": func() []requiredParam { + return []requiredParam{ + { + Name: "workerGeneratedAuthToken", + Typ: "string", + PostType: "worker_generated_auth_token", + }, + } + }, + }, + ).Parse(createTemplateStr)), + template.Must(template.New("").Funcs( + template.FuncMap{ + "snakeCase": snakeCase, + "funcName": func() string { + return "CreateControllerLed" + }, + "apiAction": func() string { + return ":create:controller-led" + }, + "extraRequiredParams": func() []requiredParam { + return nil + }, }, ).Parse(createTemplateStr)), readTemplate, @@ -803,13 +883,6 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - extraRequiredParams: []requiredParam{ - { - Name: "workerGeneratedAuthToken", - Typ: "string", - PostType: "worker_generated_auth_token", - }, - }, pluralResourceName: "workers", sliceSubtypes: map[string]sliceSubtypeInfo{ "WorkerTags": { diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index e9b4c5282..d67f11f96 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -46,7 +46,6 @@ type templateInput struct { SliceSubtypes map[string]sliceSubtypeInfo ExtraFields []fieldInfo VersionEnabled bool - ExtraRequiredParams []requiredParam CreateResponseTypes bool RecursiveListing bool } @@ -65,7 +64,6 @@ func fillTemplates() { ParentTypeName: in.parentTypeName, ExtraFields: in.extraFields, VersionEnabled: in.versionEnabled, - ExtraRequiredParams: in.extraRequiredParams, CreateResponseTypes: in.createResponseTypes, RecursiveListing: in.recursiveListing, } @@ -347,7 +345,7 @@ func (c *Client) Delete(ctx context.Context, id string, opt... Option) (*{{ .Nam `)) const createTemplateStr = ` -func (c *Client) {{ funcName }} (ctx context.Context, {{ range .ExtraRequiredParams }} {{ .Name }} {{ .Typ }}, {{ end }} {{ .CollectionFunctionArg }} string, opt... Option) (*{{ .Name }}CreateResult, error) { +func (c *Client) {{ funcName }} (ctx context.Context, {{ range extraRequiredParams }} {{ .Name }} {{ .Typ }}, {{ end }} {{ .CollectionFunctionArg }} string, opt... Option) (*{{ .Name }}CreateResult, error) { if {{ .CollectionFunctionArg }} == "" { return nil, fmt.Errorf("empty {{ .CollectionFunctionArg }} value passed into {{ funcName }} request") } @@ -357,7 +355,7 @@ func (c *Client) {{ funcName }} (ctx context.Context, {{ range .ExtraRequiredPar if c.client == nil { return nil, fmt.Errorf("nil client") } - {{ range .ExtraRequiredParams }} if {{ .Name }} == "" { + {{ range extraRequiredParams }} if {{ .Name }} == "" { return nil, fmt.Errorf("empty {{ .Name }} value passed into {{ funcName }} request") } else { opts.postMap["{{ .PostType }}"] = {{ .Name }} @@ -408,6 +406,9 @@ var commonCreateTemplate = template.Must(template.New("").Funcs( "apiAction": func() string { return "" }, + "extraRequiredParams": func() []requiredParam { + return nil + }, }, ).Parse(createTemplateStr)) diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index a76d021fa..d0f53a2ea 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -1114,6 +1114,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "create", }, nil }, + "workers create controller-led": func() (cli.Command, error) { + return &workerscmd.ControllerLedCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, "workers read": func() (cli.Command, error) { return &workerscmd.Command{ Command: base.NewCommand(ui), diff --git a/internal/cmd/commands/dev/dev.go b/internal/cmd/commands/dev/dev.go index 58f4834e3..ac503759c 100644 --- a/internal/cmd/commands/dev/dev.go +++ b/internal/cmd/commands/dev/dev.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/rand" "net" "os" "os/signal" @@ -90,7 +91,7 @@ type Command struct { flagEveryEventDenyFilters []string flagCreateLoopbackHostPlugin bool flagPluginExecutionDir string - flagUseKmsWorkerAuthMethod bool + flagWorkerAuthMethod string flagWorkerAuthStorageDir string flagWorkerAuthStorageSkipCleanup bool flagWorkerAuthRotationInterval time.Duration @@ -337,10 +338,11 @@ func (c *Command) Flags() *base.FlagSets { EnvVar: "BOUNDARY_DEV_PLUGIN_EXECUTION_DIR", Usage: "Specifies where Boundary should write plugins that it is executing; if not set defaults to system temp directory.", }) - f.BoolVar(&base.BoolVar{ - Name: "use-kms-worker-auth-method", - Target: &c.flagUseKmsWorkerAuthMethod, - Usage: "If set, the original KMS-based method of worker auth will be used to connect the initial dev worker to the controller.", + f.StringVar(&base.StringVar{ + Name: "worker-auth-method", + Target: &c.flagWorkerAuthMethod, + Default: "random-pki", + Usage: `Allows specifying how the generated worker will authenticate to the controller. Valid values are "kms" for the KMS-based mechanism; "pki-controller-led" for the PKI mechanism via the server-led authorization flow; "pki-worker-led" for the PKI mechanism via the worker-led authorization flow; and "random-pki" to randomly choose one of the PKI methods.`, }) f.StringVar(&base.StringVar{ @@ -598,7 +600,7 @@ func (c *Command) Run(args []string) int { c.InfoKeys = append(c.InfoKeys, "[Worker-Auth] AEAD Key Bytes") c.Info["[Worker-Auth] AEAD Key Bytes"] = c.Config.DevWorkerAuthKey - if !c.flagUseKmsWorkerAuthMethod { + if c.flagWorkerAuthMethod != "kms" { c.DevUsePkiForUpstream = true // These must be unset for PKI c.Config.Worker.Name = "" @@ -701,6 +703,54 @@ func (c *Command) Run(args []string) int { c.worker.TestOverrideAuthRotationPeriod = c.flagWorkerAuthRotationInterval } + // Note: this should be done before starting the worker so that the + // activation token is populated + var useWorkerLed bool + if c.flagWorkerAuthMethod != "kms" { + // Flip a coin. Use one method or the other; it's transparent to + // users, but keeps both exercised. + if c.flagWorkerAuthMethod == "random-pki" { + randPki := rand.New(rand.NewSource(time.Now().UnixMicro())).Intn(2) + if randPki == 0 { + c.flagWorkerAuthMethod = "pki-controller-led" + } else { + c.flagWorkerAuthMethod = "pki-worker-led" + } + } + switch c.flagWorkerAuthMethod { + case "pki-controller-led": + // Controller-led + serversRepo, err := c.controller.ServersRepoFn() + if err != nil { + c.UI.Error(fmt.Errorf("Error instantiating server repo: %w", err).Error()) + if err := c.controller.Shutdown(); err != nil { + c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + } + return base.CommandCliError + } + + // Create the worker in the database and fetch an activation token + worker, err := serversRepo.CreateWorker(c.Context, &server.Worker{ + Worker: &store.Worker{ + ScopeId: scope.Global.String(), + }, + }, server.WithCreateControllerLedActivationToken(true)) + if err != nil { + c.UI.Error(fmt.Errorf("Error creating worker in database: %w", err).Error()) + if err := c.controller.Shutdown(); err != nil { + c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + } + return base.CommandCliError + } + + // Set the activation token in the config + conf.RawConfig.Worker.ControllerGeneratedActivationToken = worker.ControllerGeneratedActivationToken + + default: + useWorkerLed = true + } + } + if err := c.worker.Start(); err != nil { retErr := fmt.Errorf("Error starting worker: %w", err) if err := c.worker.Shutdown(); err != nil { @@ -714,46 +764,48 @@ func (c *Command) Run(args []string) int { return base.CommandCliError } - if !c.flagUseKmsWorkerAuthMethod { - req := c.worker.WorkerAuthRegistrationRequest - if req == "" { - c.UI.Error("No worker auth registration request found at worker start time") - return base.CommandCliError - } - c.InfoKeys = append(c.InfoKeys, "worker auth registration request") - c.Info["worker auth registration request"] = req + if c.flagWorkerAuthMethod != "kms" { c.InfoKeys = append(c.InfoKeys, "worker auth current key id") c.Info["worker auth current key id"] = c.worker.WorkerAuthCurrentKeyId.Load() c.InfoKeys = append(c.InfoKeys, "worker auth storage path") c.Info["worker auth storage path"] = c.worker.WorkerAuthStorage.BaseDir() - if err := c.StoreWorkerAuthReq(c.worker.WorkerAuthRegistrationRequest, c.worker.WorkerAuthStorage.BaseDir()); err != nil { - // Shutdown on failure - retErr := fmt.Errorf("Error storing worker auth request: %w", err) - if err := c.worker.Shutdown(); err != nil { + + if useWorkerLed { + req := c.worker.WorkerAuthRegistrationRequest + if req == "" { + c.UI.Error("No worker auth registration request found at worker start time") + return base.CommandCliError + } + + if err := c.StoreWorkerAuthReq(c.worker.WorkerAuthRegistrationRequest, c.worker.WorkerAuthStorage.BaseDir()); err != nil { + // Shutdown on failure + retErr := fmt.Errorf("Error storing worker auth request: %w", err) + if err := c.worker.Shutdown(); err != nil { + c.UI.Error(retErr.Error()) + retErr = fmt.Errorf("Error shutting down worker: %w", err) + } c.UI.Error(retErr.Error()) - retErr = fmt.Errorf("Error shutting down worker: %w", err) + if err := c.controller.Shutdown(); err != nil { + c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) + } + return base.CommandCliError } - c.UI.Error(retErr.Error()) - if err := c.controller.Shutdown(); err != nil { - c.UI.Error(fmt.Errorf("Error with controller shutdown: %w", err).Error()) - } - return base.CommandCliError - } - go func() { - for { - select { - case <-c.Context.Done(): - return - case <-time.After(time.Second): - if err := authorizeWorker(c.Context, c.controller, req); err != nil { - c.UI.Error(fmt.Errorf("Error authorizing node: %w", err).Error()) - errorEncountered.Store(true) + go func() { + for { + select { + case <-c.Context.Done(): + return + case <-time.After(time.Second): + if err := authorizeWorker(c.Context, c.controller, req); err != nil { + c.UI.Error(fmt.Errorf("Error authorizing node: %w", err).Error()) + errorEncountered.Store(true) + return + } return } - return } - } - }() + }() + } } } diff --git a/internal/cmd/commands/dev/flags_test.go b/internal/cmd/commands/dev/flags_test.go index f52baea7d..8f4fd3bd0 100644 --- a/internal/cmd/commands/dev/flags_test.go +++ b/internal/cmd/commands/dev/flags_test.go @@ -64,7 +64,7 @@ func TestCommand_Flags(t *testing.T) { assert.Contains(completions, "-event-allow-filter") assert.Contains(completions, "-event-deny-filter") assert.Contains(completions, "-plugin-execution-dir") - assert.Contains(completions, "-use-kms-worker-auth-method") + assert.Contains(completions, "-worker-auth-method") assert.Contains(completions, "-worker-auth-storage-dir") assert.Contains(completions, "-worker-auth-storage-skip-cleanup") diff --git a/internal/cmd/commands/server/server.go b/internal/cmd/commands/server/server.go index b7a713548..af4e2958a 100644 --- a/internal/cmd/commands/server/server.go +++ b/internal/cmd/commands/server/server.go @@ -210,6 +210,10 @@ func (c *Command) Run(args []string) int { c.UI.Error("Worker is using KMS auth but has no name set. It must be the unique name of this instance.") return base.CommandUserError } + if c.Config.Worker.ControllerGeneratedActivationToken != "" { + c.UI.Error("Worker has KMS auth info but also has a controller-generated activation token set, which is incompatible.") + return base.CommandUserError + } } } diff --git a/internal/cmd/commands/workerscmd/controller-led_funcs.go b/internal/cmd/commands/workerscmd/controller-led_funcs.go new file mode 100644 index 000000000..b34853ee5 --- /dev/null +++ b/internal/cmd/commands/workerscmd/controller-led_funcs.go @@ -0,0 +1,40 @@ +package workerscmd + +import ( + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/workers" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + executeExtraControllerLedActions = executeExtraControllerLedActionsImpl +} + +func (c *ControllerLedCommand) extraControllerLedHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary workers create controller-led [options] [args]", + "", + " Create a worker using the controller-led approach, receiving an activation token for a worker. Example:", + "", + ` $ boundary workers create controller-led -name us-east-1-1 "`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} + +func executeExtraControllerLedActionsImpl(c *ControllerLedCommand, origResp *api.Response, origItem *workers.Worker, origError error, workerClient *workers.Client, version uint32, opts []workers.Option) (*api.Response, *workers.Worker, error) { + switch c.Func { + case "create": + result, err := workerClient.CreateControllerLed(c.Context, c.FlagScopeId, opts...) + if err != nil { + return nil, nil, err + } + return result.GetResponse(), result.GetItem(), nil + } + return origResp, origItem, origError +} diff --git a/internal/cmd/commands/workerscmd/controller-led_workers.gen.go b/internal/cmd/commands/workerscmd/controller-led_workers.gen.go new file mode 100644 index 000000000..ddc627d89 --- /dev/null +++ b/internal/cmd/commands/workerscmd/controller-led_workers.gen.go @@ -0,0 +1,247 @@ +// Code generated by "make cli"; DO NOT EDIT. +package workerscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/workers" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initControllerLedFlags() { + flagsOnce.Do(func() { + extraFlags := extraControllerLedActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsControllerLedMap[k] = append(flagsControllerLedMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*ControllerLedCommand)(nil) + _ cli.CommandAutocomplete = (*ControllerLedCommand)(nil) +) + +type ControllerLedCommand struct { + *base.Command + + Func string + + plural string +} + +func (c *ControllerLedCommand) AutocompleteArgs() complete.Predictor { + initControllerLedFlags() + return complete.PredictAnything +} + +func (c *ControllerLedCommand) AutocompleteFlags() complete.Flags { + initControllerLedFlags() + return c.Flags().Completions() +} + +func (c *ControllerLedCommand) Synopsis() string { + if extra := extraControllerLedSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "worker" + + synopsisStr = fmt.Sprintf("%s %s", "controller-led-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *ControllerLedCommand) Help() string { + initControllerLedFlags() + + var helpStr string + helpMap := common.HelpMap("worker") + + switch c.Func { + + default: + + helpStr = c.extraControllerLedHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsControllerLedMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, +} + +func (c *ControllerLedCommand) Flags() *base.FlagSets { + if len(flagsControllerLedMap[c.Func]) == 0 { + return c.FlagSet(base.FlagSetNone) + } + + set := c.FlagSet(base.FlagSetHTTP | base.FlagSetClient | base.FlagSetOutputFormat) + f := set.NewFlagSet("Command Options") + common.PopulateCommonFlags(c.Command, f, "controller-led-type worker", flagsControllerLedMap, c.Func) + + extraControllerLedFlagsFunc(c, set, f) + + return set +} + +func (c *ControllerLedCommand) Run(args []string) int { + initControllerLedFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "update": + return cli.RunResultHelp + + } + + c.plural = "controller-led-type worker" + switch c.Func { + case "list": + c.plural = "controller-led-type workers" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsControllerLedMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []workers.Option + + if strutil.StrListContains(flagsControllerLedMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + workersClient := workers.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, workers.DefaultName()) + default: + opts = append(opts, workers.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, workers.DefaultDescription()) + default: + opts = append(opts, workers.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, workers.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, workers.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraControllerLedFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var resp *api.Response + var item *workers.Worker + + switch c.Func { + + } + + resp, item, err = executeExtraControllerLedActions(c, resp, item, err, workersClient, version, opts) + if exitCode := c.checkFuncError(err); exitCode > 0 { + return exitCode + } + + output, err := printCustomControllerLedActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(item, resp)) + + case "json": + if ok := c.PrintJsonItem(resp); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +func (c *ControllerLedCommand) checkFuncError(err error) int { + if err == nil { + return 0 + } + if apiErr := api.AsServerError(err); apiErr != nil { + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError +} + +var ( + extraControllerLedActionsFlagsMapFunc = func() map[string][]string { return nil } + extraControllerLedSynopsisFunc = func(*ControllerLedCommand) string { return "" } + extraControllerLedFlagsFunc = func(*ControllerLedCommand, *base.FlagSets, *base.FlagSet) {} + extraControllerLedFlagsHandlingFunc = func(*ControllerLedCommand, *base.FlagSets, *[]workers.Option) bool { return true } + executeExtraControllerLedActions = func(_ *ControllerLedCommand, inResp *api.Response, inItem *workers.Worker, inErr error, _ *workers.Client, _ uint32, _ []workers.Option) (*api.Response, *workers.Worker, error) { + return inResp, inItem, inErr + } + printCustomControllerLedActionOutput = func(*ControllerLedCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/workerscmd/funcs.go b/internal/cmd/commands/workerscmd/funcs.go index 328484ddc..cb8027760 100644 --- a/internal/cmd/commands/workerscmd/funcs.go +++ b/internal/cmd/commands/workerscmd/funcs.go @@ -261,6 +261,9 @@ func printItemTable(item *workers.Worker, resp *api.Response) string { if !item.LastStatusTime.IsZero() { nonAttributeMap["Last Status Time"] = item.LastStatusTime } + if item.ControllerGeneratedActivationToken != "" { + nonAttributeMap["Controller-Generated Activation Token"] = item.ControllerGeneratedActivationToken + } resultMap := resp.Map if count, ok := resultMap[globals.ActiveConnectionCountField]; ok { diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index 0a69215a5..1c1de5600 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -212,6 +212,11 @@ type Worker struct { // AuthStoragePath represents the location a worker stores its node credentials, if set AuthStoragePath string `hcl:"auth_storage_path"` + + // ControllerGeneratedActivationToken is a controller-generated activation + // token used to register this worker to the cluster. It can be a path, env + // var, or direct value. + ControllerGeneratedActivationToken string `hcl:"controller_generated_activation_token"` } type Database struct { @@ -454,6 +459,11 @@ func Parse(d string) (*Config, error) { return nil, errors.New("Worker description contains non-printable characters") } + result.Worker.ControllerGeneratedActivationToken, err = parseutil.ParsePath(result.Worker.ControllerGeneratedActivationToken) + if err != nil && !errors.Is(err, parseutil.ErrNotAUrl) { + return nil, fmt.Errorf("Error parsing worker activation token: %w", err) + } + if result.Worker.TagsRaw != nil { switch t := result.Worker.TagsRaw.(type) { // We allow `tags` to be a simple string containing a URL with schema. diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 1f4ac86f1..62126b214 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -301,6 +301,24 @@ func TestDevWorker(t *testing.T) { _, err = Parse(devConfig + devWorkerKeyValueConfig) assert.Error(t, err) + + // Check activation token parsing + devWorkerActivationTokenConfig := ` + listener "tcp" { + purpose = "proxy" + } + + worker { + name = "dev-worker" + description = "A default worker created in dev mode" + initial_upstreams = ["127.0.0.1"] + controller_generated_activation_token = "foobar" + } + ` + + actual, err = Parse(devConfig + devWorkerActivationTokenConfig) + require.NoError(t, err) + assert.Equal(t, "foobar", actual.Worker.ControllerGeneratedActivationToken) } func TestDevCombined(t *testing.T) { diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 65b50489e..47daab46a 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -552,5 +552,19 @@ var inputStructs = map[string][]*cmdInfo{ NeedsSubtypeInCreate: true, SkipClientCallActions: []string{"create"}, }, + { + ResourceType: resource.Worker.String(), + Pkg: "workers", + StdActions: []string{"create"}, + SubActionPrefix: "controller-led", + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + Container: "Scope", + HasDescription: true, + NeedsSubtypeInCreate: true, + SkipClientCallActions: []string{"create"}, + }, }, } diff --git a/internal/daemon/cluster/handlers/worker_service_test.go b/internal/daemon/cluster/handlers/worker_service_test.go index 298149059..629159355 100644 --- a/internal/daemon/cluster/handlers/worker_service_test.go +++ b/internal/daemon/cluster/handlers/worker_service_test.go @@ -2,6 +2,7 @@ package handlers_test import ( "context" + "crypto/rand" "fmt" "sync" "testing" @@ -20,6 +21,7 @@ import ( "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/target/tcp" + "github.com/hashicorp/boundary/internal/types/scope" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh/testdata" @@ -206,16 +208,17 @@ func TestHcpbWorkers(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - kms := kms.TestKms(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) serversRepoFn := func() (*server.Repository, error) { - return server.NewRepository(rw, rw, kms) + return server.NewRepository(rw, rw, kmsCache) } sessionRepoFn := func() (*session.Repository, error) { - return session.NewRepository(rw, rw, kms) + return session.NewRepository(rw, rw, kmsCache) } connectionRepoFn := func() (*session.ConnectionRepository, error) { - return session.NewConnectionRepository(ctx, rw, rw, kms) + return session.NewConnectionRepository(ctx, rw, rw, kmsCache) } for i := 0; i < 3; i++ { @@ -228,7 +231,7 @@ func TestHcpbWorkers(t *testing.T) { server.TestPkiWorker(t, conn, wrapper, opt...) } - s := handlers.NewWorkerServiceServer(serversRepoFn, sessionRepoFn, connectionRepoFn, new(sync.Map), kms) + s := handlers.NewWorkerServiceServer(serversRepoFn, sessionRepoFn, connectionRepoFn, new(sync.Map), kmsCache) require.NotNil(t, s) res, err := s.ListHcpbWorkers(ctx, &pbs.ListHcpbWorkersRequest{}) diff --git a/internal/daemon/controller/handlers/scopes/scope_service_test.go b/internal/daemon/controller/handlers/scopes/scope_service_test.go index 470df83a6..cb38906f4 100644 --- a/internal/daemon/controller/handlers/scopes/scope_service_test.go +++ b/internal/daemon/controller/handlers/scopes/scope_service_test.go @@ -97,6 +97,7 @@ var globalAuthorizedCollectionActions = map[string]*structpb.ListValue{ }, "workers": { Values: []*structpb.Value{ + structpb.NewStringValue("create:controller-led"), structpb.NewStringValue("create:worker-led"), structpb.NewStringValue("list"), }, diff --git a/internal/daemon/controller/handlers/targets/target_service_test.go b/internal/daemon/controller/handlers/targets/target_service_test.go index 760b25d9d..7ec054972 100644 --- a/internal/daemon/controller/handlers/targets/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/target_service_test.go @@ -1,10 +1,13 @@ package targets import ( + "context" + "crypto/rand" "fmt" "testing" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/types/scope" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" @@ -35,6 +38,8 @@ func TestWorkerList_Addresses(t *testing.T) { func TestWorkerList_Filter(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) var workers []*server.Worker for i := 0; i < 5; i++ { switch { diff --git a/internal/daemon/controller/handlers/workers/worker_service.go b/internal/daemon/controller/handlers/workers/worker_service.go index 9eb06ba33..215a6b582 100644 --- a/internal/daemon/controller/handlers/workers/worker_service.go +++ b/internal/daemon/controller/handlers/workers/worker_service.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/boundary/internal/types/action" "github.com/hashicorp/boundary/internal/types/resource" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/boundary/internal/util" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/workers" "github.com/hashicorp/go-secure-stdlib/strutil" "github.com/hashicorp/nodeenrollment/types" @@ -52,6 +53,7 @@ var ( // CollectionActions contains the set of actions that can be performed on // this collection CollectionActions = action.ActionSet{ + action.CreateControllerLed, action.CreateWorkerLed, action.List, } @@ -199,18 +201,20 @@ func (s Service) GetWorker(ctx context.Context, req *pbs.GetWorkerRequest) (*pbs return &pbs.GetWorkerResponse{Item: item}, nil } -// CreateWorker implements the interface pbs.WorkerServiceServer. +// CreateWorkerLed implements the interface pbs.WorkerServiceServer and handles +// a request to create a new worker and consume a worker-generated authorization +// request func (s Service) CreateWorkerLed(ctx context.Context, req *pbs.CreateWorkerLedRequest) (*pbs.CreateWorkerLedResponse, error) { const op = "workers.(Service).CreateWorkerLed" - if err := validateCreateRequest(req); err != nil { + act := action.CreateWorkerLed + item := req.GetItem() + + if err := validateCreateRequest(item, act); err != nil { return nil, err } - authResults := s.authResult(ctx, req.GetItem().GetScopeId(), action.CreateWorkerLed) - if authResults.Error != nil { - return nil, authResults.Error - } - reqBytes, err := base58.FastBase58Decoding(req.GetItem().WorkerGeneratedAuthToken.GetValue()) + + reqBytes, err := base58.FastBase58Decoding(item.WorkerGeneratedAuthToken.GetValue()) if err != nil { return nil, fmt.Errorf("%s: error decoding node_credentials_token: %w", op, err) } @@ -219,7 +223,43 @@ func (s Service) CreateWorkerLed(ctx context.Context, req *pbs.CreateWorkerLedRe if err := proto.Unmarshal(reqBytes, creds); err != nil { return nil, fmt.Errorf("%s: error unmarshaling node_credentials_token: %w", op, err) } - created, err := s.createInRepo(ctx, req.GetItem(), creds) + + out, err := s.createCommon(ctx, item, act, server.WithFetchNodeCredentialsRequest(creds)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + return &pbs.CreateWorkerLedResponse{Item: out}, nil +} + +// CreateControllerLed implements the interface pbs.WorkerServiceServer and handles +// a request to create a new worker, generating and returning an activation +// token +func (s Service) CreateControllerLed(ctx context.Context, req *pbs.CreateControllerLedRequest) (*pbs.CreateControllerLedResponse, error) { + const op = "workers.(Service).CreateControllerLed" + act := action.CreateControllerLed + + if err := validateCreateRequest(req.GetItem(), act); err != nil { + return nil, err + } + + out, err := s.createCommon(ctx, req.GetItem(), act, server.WithCreateControllerLedActivationToken(true)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + return &pbs.CreateControllerLedResponse{Item: out}, nil +} + +func (s Service) createCommon(ctx context.Context, in *pb.Worker, act action.Type, opt ...server.Option) (*pb.Worker, error) { + const op = "workers.(Service).createCommon" + + authResults := s.authResult(ctx, in.GetScopeId(), act) + if authResults.Error != nil { + return nil, authResults.Error + } + + created, err := s.createInRepo(ctx, in, opt...) if err != nil { return nil, fmt.Errorf("%s: error creating worker: %w", op, err) } @@ -243,7 +283,7 @@ func (s Service) CreateWorkerLed(ctx context.Context, req *pbs.CreateWorkerLedRe return nil, err } - return &pbs.CreateWorkerLedResponse{Item: item}, nil + return item, nil } // DeleteWorker implements the interface pbs.WorkerServiceServer. @@ -438,7 +478,7 @@ func (s Service) getFromRepo(ctx context.Context, id string) (*server.Worker, er return w, nil } -func (s Service) createInRepo(ctx context.Context, worker *pb.Worker, creds *types.FetchNodeCredentialsRequest) (*server.Worker, error) { +func (s Service) createInRepo(ctx context.Context, worker *pb.Worker, opt ...server.Option) (*server.Worker, error) { const op = "workers.(Service).createInRepo" repo, err := s.repoFn() if err != nil { @@ -449,7 +489,7 @@ func (s Service) createInRepo(ctx context.Context, worker *pb.Worker, creds *typ server.WithName(worker.GetName().GetValue()), server.WithDescription(worker.GetDescription().GetValue()), ) - retWorker, err := repo.CreateWorker(ctx, newWorker, server.WithFetchNodeCredentialsRequest(creds)) + retWorker, err := repo.CreateWorker(ctx, newWorker, opt...) if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker")) } @@ -593,7 +633,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. var parentId string opts := []auth.Option{auth.WithType(resource.Worker), auth.WithAction(a)} switch a { - case action.List, action.CreateWorkerLed: + case action.List, action.CreateWorkerLed, action.CreateControllerLed: parentId = id default: w, err := repo.LookupWorker(ctx, id) @@ -670,6 +710,9 @@ func toProto(ctx context.Context, in *server.Worker, opt ...handlers.Option) (*p if outputFields.Has(globals.ActiveConnectionCountField) { out.ActiveConnectionCount = &wrapperspb.UInt32Value{Value: in.ActiveConnectionCount()} } + if outputFields.Has(globals.ControllerGeneratedActivationToken) && in.ControllerGeneratedActivationToken != "" { + out.ControllerGeneratedActivationToken = &wrapperspb.StringValue{Value: in.ControllerGeneratedActivationToken} + } if outputFields.Has(globals.ConfigTagsField) && len(in.GetConfigTags()) > 0 { var err error out.ConfigTags, err = tagsToMapProto(in.GetConfigTags()) @@ -769,45 +812,57 @@ func validateUpdateRequest(req *pbs.UpdateWorkerRequest) error { }, server.WorkerPrefix) } -func validateCreateRequest(req *pbs.CreateWorkerLedRequest) error { - return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { +func validateCreateRequest(item *pb.Worker, act action.Type) error { + if util.IsNil(item) { + return handlers.InvalidArgumentErrorf("Request item is nil", nil) + } + switch act { + case action.CreateWorkerLed: + case action.CreateControllerLed: + default: + // This shouldn't happen because we shouldn't be routed to one of the + // handlers if it's the wrong action, but check anyways. + return handlers.InvalidArgumentErrorf("Invalid action", nil) + } + return handlers.ValidateCreateRequest(item, func() map[string]string { const ( mustBeGlobalMsg = "Must be 'global'" cannotBeEmptyMsg = "Cannot be empty." readOnlyFieldMsg = "This is a read only field." ) badFields := map[string]string{} - if scope.Global.String() != req.GetItem().GetScopeId() { + if scope.Global.String() != item.GetScopeId() { badFields[globals.ScopeIdField] = mustBeGlobalMsg } - // FIXME: in the future, we won't require this token since we'll support - // the server led flow where a token is returned when a worker is created. - if req.GetItem().WorkerGeneratedAuthToken == nil { + switch { + case act == action.CreateWorkerLed && item.WorkerGeneratedAuthToken == nil: badFields[globals.WorkerGeneratedAuthTokenField] = cannotBeEmptyMsg + case act == action.CreateControllerLed && item.WorkerGeneratedAuthToken != nil: + badFields[globals.WorkerGeneratedAuthTokenField] = "Worker-generated auth tokens are not used with the controller-led creation flow." } - if req.GetItem().Address != "" { + if item.Address != "" { badFields[globals.CanonicalAddressField] = readOnlyFieldMsg } - if req.GetItem().CanonicalTags != nil { + if item.CanonicalTags != nil { badFields[globals.CanonicalTagsField] = readOnlyFieldMsg } - if req.GetItem().ConfigTags != nil { + if item.ConfigTags != nil { badFields[globals.ConfigTagsField] = readOnlyFieldMsg } - if req.GetItem().LastStatusTime != nil { + if item.LastStatusTime != nil { badFields[globals.LastStatusTimeField] = readOnlyFieldMsg } - if req.GetItem().AuthorizedActions != nil { + if item.AuthorizedActions != nil { badFields[globals.AuthorizedActionsField] = readOnlyFieldMsg } - nameString := req.GetItem().GetName().String() + nameString := item.GetName().String() if !strutil.Printable(nameString) { badFields[globals.NameField] = "Name contains non-printable characters." } if strings.ToLower(nameString) != nameString { badFields[globals.NameField] = "Name must be all lowercase." } - descriptionString := req.GetItem().GetDescription().String() + descriptionString := item.GetDescription().String() if !strutil.Printable(descriptionString) { badFields[globals.DescriptionField] = "Description contains non-printable characters." } diff --git a/internal/daemon/controller/handlers/workers/worker_service_test.go b/internal/daemon/controller/handlers/workers/worker_service_test.go index 2e222311f..7da8bc177 100644 --- a/internal/daemon/controller/handlers/workers/worker_service_test.go +++ b/internal/daemon/controller/handlers/workers/worker_service_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "sort" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -22,6 +23,7 @@ import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/workers" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/workers" "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/hashicorp/nodeenrollment" "github.com/hashicorp/nodeenrollment/rotation" "github.com/hashicorp/nodeenrollment/storage/file" "github.com/hashicorp/nodeenrollment/types" @@ -1316,6 +1318,350 @@ func TestCreateWorkerLed(t *testing.T) { } } +func TestCreateControllerLed(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + testRootWrapper := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, testRootWrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + rw := db.New(conn) + testKms := kms.TestKms(t, conn, testRootWrapper) + repoFn := func() (*server.Repository, error) { + return server.NewRepository(rw, rw, testKms) + } + testCtx := context.Background() + + testSrv, err := NewService(testCtx, repoFn, iamRepoFn) + require.NoError(t, err, "Error when getting new worker service.") + + // Get an initial set of authorized node credentials + rootStorage, err := server.NewRepositoryStorage(testCtx, rw, rw, testKms) + require.NoError(t, err) + _, err = rotation.RotateRootCertificates(testCtx, rootStorage) + require.NoError(t, err) + + fetchReqFn := func() string { + // This happens on the worker + fileStorage, err := file.New(testCtx) + require.NoError(t, err) + defer fileStorage.Cleanup() + + nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage) + require.NoError(t, err) + // Create request using worker id + fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx) + require.NoError(t, err) + + fetchEncoded, err := proto.Marshal(fetchReq) + require.NoError(t, err) + + return base58.Encode(fetchEncoded) + } + + tests := []struct { + name string + scopeId string + service Service + req *pbs.CreateControllerLedRequest + res *pbs.CreateControllerLedResponse + wantErr bool + wantErrIs error + wantErrContains string + }{ + { + name: "invalid-scope", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: "invalid-scope", + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: "Must be 'global'", + }, + { + name: "supplied-node-auth-request", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + WorkerGeneratedAuthToken: &wrapperspb.StringValue{Value: fetchReqFn()}, + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.WorkerGeneratedAuthTokenField, + }, + { + name: "invalid-id", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Id: "invalid-id", + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.IdField, + }, + { + name: "invalid-address", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Address: "invalid-address", + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.AddressField, + }, + { + name: "invalid-config-tags", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + ConfigTags: map[string]*structpb.ListValue{ + "invalid": {Values: []*structpb.Value{ + structpb.NewStringValue("invalid-tags"), + }}, + }, + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.ConfigTagsField, + }, + { + name: "invalid-canonical-tags", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + CanonicalTags: map[string]*structpb.ListValue{ + "invalid": {Values: []*structpb.Value{ + structpb.NewStringValue("invalid-canonical-tags"), + }}, + }, + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.CanonicalTagsField, + }, + { + name: "invalid-last-status-time", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + LastStatusTime: timestamppb.Now(), + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.LastStatusTimeField, + }, + { + name: "invalid-authorized-actions", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + AuthorizedActions: []string{"invalid-authorized-actions"}, + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.AuthorizedActionsField, + }, + { + name: "invalid-create-time", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + CreatedTime: timestamppb.Now(), + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.CreatedTimeField, + }, + { + name: "invalid-update-time", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + UpdatedTime: timestamppb.Now(), + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.UpdatedTimeField, + }, + { + name: "invalid-version", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Version: 1, + }, + }, + wantErr: true, + wantErrIs: handlers.ApiErrorWithCode(codes.InvalidArgument), + wantErrContains: globals.VersionField, + }, + { + name: "invalid-auth", + service: testSrv, + scopeId: "splat-auth-scope", + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + }, + }, + wantErr: true, + wantErrContains: "non-existent scope \"splat-auth-scope\"", + }, + { + name: "create-error", + service: func() Service { + repoFn := func() (*server.Repository, error) { + return server.NewRepository(rw, &db.Db{}, testKms) + } + testSrv, err := NewService(testCtx, repoFn, iamRepoFn) + require.NoError(t, err, "Error when getting new worker service.") + return testSrv + }(), + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Name: &wrapperspb.StringValue{Value: "success"}, + Description: &wrapperspb.StringValue{Value: "success-description"}, + }, + }, + wantErr: true, + wantErrContains: "error creating worker", + }, + { + name: "bad-repo-function-in-create", + service: func() Service { + // var cnt gives us a way for the repoFn to fail the 2nd time + // it's called so we can get past the AuthRequest check in + // CreateWorker(...) and test the createInRepo(...) failure + // path if the repoFn returns and err. + cnt := 0 + repoFn := func() (*server.Repository, error) { + cnt = cnt + 1 + switch { + case cnt > 1: + return nil, errors.New(testCtx, errors.Internal, "bad-repo-function", "error creating repo") + default: + return server.NewRepository(rw, rw, testKms) + } + } + testSrv, err := NewService(testCtx, repoFn, iamRepoFn) + require.NoError(t, err, "Error when getting new worker service.") + return testSrv + }(), + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Name: &wrapperspb.StringValue{Value: "success"}, + Description: &wrapperspb.StringValue{Value: "success-description"}, + }, + }, + wantErr: true, + wantErrContains: "error creating worker: bad-repo-function: error creating repo", + }, + { + name: "success", + service: testSrv, + scopeId: scope.Global.String(), + req: &pbs.CreateControllerLedRequest{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Name: &wrapperspb.StringValue{Value: "success"}, + Description: &wrapperspb.StringValue{Value: "success-description"}, + }, + }, + res: &pbs.CreateControllerLedResponse{ + Item: &workers.Worker{ + ScopeId: scope.Global.String(), + Name: &wrapperspb.StringValue{Value: "success"}, + Description: &wrapperspb.StringValue{Value: "success-description"}, + ActiveConnectionCount: &wrapperspb.UInt32Value{Value: 0}, + Version: 1, + Type: PkiWorkerType, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := tc.service.CreateControllerLed(auth.DisabledAuthTestContext(iamRepoFn, tc.scopeId), tc.req) + if tc.wantErr { + require.Error(err) + assert.Nil(got) + if tc.wantErrIs != nil { + assert.ErrorIs(err, tc.wantErrIs) + } + if tc.wantErrContains != "" { + assert.Contains(err.Error(), tc.wantErrContains) + } + return + } + require.NoError(err) + require.NotNil(got) + { + // we only need to check that these "read-only" fields were set + // outbound. Other portions of the domain is responsible for + // testing that these values are correct and there's no reason + // to repeat those tests here. + assert.NotEmpty(got.GetItem().GetId()) + assert.NotEmpty(got.GetItem().CreatedTime) + assert.NotEmpty(got.GetItem().UpdatedTime) + assert.NotEmpty(got.GetItem().Scope) + assert.NotEmpty(got.GetItem().AuthorizedActions) + assert.NotEmpty(got.GetItem().ControllerGeneratedActivationToken) + assert.True(strings.HasPrefix(got.GetItem().ControllerGeneratedActivationToken.GetValue(), nodeenrollment.ServerLedActivationTokenPrefix)) + { + tc.res.Item.Id = got.GetItem().GetId() + tc.res.Item.CreatedTime = got.GetItem().GetCreatedTime() + tc.res.Item.UpdatedTime = got.GetItem().GetUpdatedTime() + tc.res.Item.Scope = got.GetItem().Scope + tc.res.Item.AuthorizedActions = got.GetItem().GetAuthorizedActions() + tc.res.Item.ControllerGeneratedActivationToken = got.GetItem().GetControllerGeneratedActivationToken() + } + } + assert.Equal(tc.res, got) + }) + } +} + func TestService_AddWorkerTags(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) @@ -1662,8 +2008,9 @@ func TestService_RemoveWorkerTags(t *testing.T) { name: "remove-valid-tag", req: func() *pbs.RemoveWorkerTagsRequest { worker := server.TestKmsWorker(t, conn, wrapper) - s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, + _, err := s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, worker.Version, map[string]*structpb.ListValue{"key": {Values: []*structpb.Value{structpb.NewStringValue("value")}}}) + require.NoError(err) return &pbs.RemoveWorkerTagsRequest{ Id: worker.PublicId, Version: worker.Version + 1, @@ -1676,7 +2023,7 @@ func TestService_RemoveWorkerTags(t *testing.T) { name: "remove-many-valid-tags", req: func() *pbs.RemoveWorkerTagsRequest { worker := server.TestKmsWorker(t, conn, wrapper) - s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, + _, err := s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, worker.Version, map[string]*structpb.ListValue{ "key": {Values: []*structpb.Value{structpb.NewStringValue("value"), structpb.NewStringValue("value1")}}, "key2": {Values: []*structpb.Value{structpb.NewStringValue("value2")}}, @@ -1685,6 +2032,7 @@ func TestService_RemoveWorkerTags(t *testing.T) { "key5": {Values: []*structpb.Value{structpb.NewStringValue("value5")}}, "key6": {Values: []*structpb.Value{structpb.NewStringValue("value")}}, }) + require.NoError(err) return &pbs.RemoveWorkerTagsRequest{ Id: worker.PublicId, Version: worker.Version + 1, @@ -1738,7 +2086,7 @@ func TestService_RemoveWorkerTags(t *testing.T) { name: "remove-nonexistent-tags", req: func() *pbs.RemoveWorkerTagsRequest { worker := server.TestKmsWorker(t, conn, wrapper) - s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, + _, err := s.addTagsInRepo(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), worker.PublicId, worker.Version, map[string]*structpb.ListValue{ "key": {Values: []*structpb.Value{structpb.NewStringValue("value")}}, "key2": {Values: []*structpb.Value{structpb.NewStringValue("value2")}}, @@ -1747,6 +2095,7 @@ func TestService_RemoveWorkerTags(t *testing.T) { "key5": {Values: []*structpb.Value{structpb.NewStringValue("value5")}}, "key6": {Values: []*structpb.Value{structpb.NewStringValue("value")}}, }) + require.NoError(err) return &pbs.RemoveWorkerTagsRequest{ Id: worker.PublicId, Version: worker.Version + 1, diff --git a/internal/daemon/worker/controller_connection.go b/internal/daemon/worker/controller_connection.go index d779a6c75..e8bb3355f 100644 --- a/internal/daemon/worker/controller_connection.go +++ b/internal/daemon/worker/controller_connection.go @@ -86,7 +86,16 @@ func (w *Worker) controllerDialerFunc(extraAlpnProtos ...string) func(context.Co case w.conf.WorkerAuthKms != nil && !w.conf.DevUsePkiForUpstream: conn, err = w.v1KmsAuthDialFn(ctx, addr, extraAlpnProtos...) default: - conn, err = protocol.Dial(ctx, w.WorkerAuthStorage, addr, nodeenrollment.WithWrapper(w.conf.WorkerAuthStorageKms), nodeenrollment.WithExtraAlpnProtos(extraAlpnProtos)) + conn, err = protocol.Dial( + ctx, + w.WorkerAuthStorage, + addr, + nodeenrollment.WithWrapper(w.conf.WorkerAuthStorageKms), + nodeenrollment.WithExtraAlpnProtos(extraAlpnProtos), + // If the activation token hasn't been populated, this won't do + // anything, and it won't do anything if it's already been used + nodeenrollment.WithActivationToken(w.conf.RawConfig.Worker.ControllerGeneratedActivationToken), + ) // No error and a valid connection means the WorkerAuthRegistrationRequest was populated // We can remove the stored workerAuthRequest file if err == nil && conn != nil { @@ -97,13 +106,23 @@ func (w *Worker) controllerDialerFunc(extraAlpnProtos ...string) func(context.Co } } } - switch err { - case nil: - case nodeenrollment.ErrNotAuthorized: - // We don't event in this case, because the function retries often - // and will spam the logs. The status function will event indicating - // that it can't send status because it's not authorized, so that - // will be a fine hint to the user as to the issue. + switch { + case err == nil: + // Nothing + + case errors.Is(err, nodeenrollment.ErrNotAuthorized): + switch w.conf.RawConfig.Worker.ControllerGeneratedActivationToken { + case "": + // We don't event in this case, because the function retries + // often and will spam the logs while waiting on the user to + // transfer the worker-generated request over + + default: + // In this case, event, so that the operator can understand that + // it was rejected + event.WriteError(ctx, op, fmt.Errorf("controller rejected activation token as invalid")) + } + default: event.WriteError(ctx, op, err) } diff --git a/internal/daemon/worker/worker.go b/internal/daemon/worker/worker.go index e5eb86feb..531ec53b6 100644 --- a/internal/daemon/worker/worker.go +++ b/internal/daemon/worker/worker.go @@ -251,6 +251,10 @@ func (w *Worker) Start() error { // the controller, we don't want to invalidate that request on restart // by generating a new set of credentials. However it's safe to output a // new fetch request so we do in fact do that. + // + // Note that if a controller-generated activation token has been + // supplied, we do not output a fetch request; we attempt to use that + // directly later. var err error w.WorkerAuthStorage, err = nodeefile.New(w.baseContext, nodeefile.WithBaseDirectory(w.conf.RawConfig.Worker.AuthStoragePath)) @@ -311,6 +315,15 @@ func (w *Worker) Start() error { return fmt.Errorf("error loading worker auth creds: %w", err) } + // Don't output a fetch request if an activation token has been + // provided. Technically we _could_ still output a fetch request, and it + // would be valid to do so, but if a token was provided it may well be + // confusing to a user if it seems like it was ignored because a fetch + // request was still output. + if actToken := w.conf.RawConfig.Worker.ControllerGeneratedActivationToken; actToken != "" { + createFetchRequest = false + } + // NOTE: this block _must_ be before the `if createFetchRequest` block // or the fetch request may have no credentials to work with if createNodeAuthCreds { diff --git a/internal/db/schema/migrations/oss/postgres/48/01_worker_auth_activation_token.up.sql b/internal/db/schema/migrations/oss/postgres/48/01_worker_auth_activation_token.up.sql new file mode 100644 index 000000000..4b9d053f8 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/48/01_worker_auth_activation_token.up.sql @@ -0,0 +1,30 @@ +begin; + +create table worker_auth_server_led_activation_token( + worker_id wt_public_id primary key + constraint server_worker_fkey + references server_worker (public_id) + on delete cascade + on update cascade, + token_id text not null + constraint token_id_must_not_be_empty + check(length(trim(token_id)) > 0), + creation_time_encrypted bytea not null + constraint creation_time_encrypted_must_not_be_empty + check(length(creation_time_encrypted) > 0), + key_id kms_private_id not null + constraint kms_data_key_version_fkey + references kms_data_key_version (private_id) + on delete restrict + on update cascade, + + constraint worker_auth_server_led_activation_token_token_id_uq -- only one valid token at a time for a given worker + unique(token_id) +); +comment on table worker_auth_server_led_activation_token is + 'worker_auth_server_led_activation_token is a table where each row represents an activation token for a worker. Only one activation token is allowed per worker.'; + +create trigger immutable_columns before update on worker_auth_server_led_activation_token + for each row execute procedure immutable_columns('worker_id', 'token_id', 'creation_time_encrypted'); + +commit; diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index f73ef31c4..a236e45d5 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -3961,6 +3961,33 @@ ] } }, + "/v1/workers:create:controller-led": { + "post": { + "summary": "Creates a single Worker.", + "operationId": "WorkerService_CreateControllerLed", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.workers.v1.Worker" + } + } + }, + "parameters": [ + { + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.workers.v1.Worker" + } + } + ], + "tags": [ + "controller.api.services.v1.WorkerService" + ] + } + }, "/v1/workers:create:worker-led": { "post": { "summary": "Creates a single Worker.", @@ -5790,6 +5817,11 @@ "type": "string", "title": "worker_generated_auth_token is input only. Supports the worker led node\nenrollment flow where this credentials token is produced by a worker. This\ntoken is a base58 encoded types.FetchNodeCredentialsRequest from\nhttps://github.com/hashicorp/nodeenrollment" }, + "controller_generated_activation_token": { + "type": "string", + "description": "Output only. An activation token that can be given to a worker to correlate\nit to the created resource.", + "readOnly": true + }, "active_connection_count": { "type": "integer", "format": "int64", @@ -5966,6 +5998,17 @@ } } }, + "controller.api.services.v1.CreateControllerLedResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "item": { + "$ref": "#/definitions/controller.api.resources.workers.v1.Worker" + } + } + }, "controller.api.services.v1.CreateCredentialLibraryResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/worker_service.pb.go b/internal/gen/controller/api/services/worker_service.pb.go index bd94f8042..1c3ce5d1b 100644 --- a/internal/gen/controller/api/services/worker_service.pb.go +++ b/internal/gen/controller/api/services/worker_service.pb.go @@ -331,6 +331,108 @@ func (x *CreateWorkerLedResponse) GetItem() *workers.Worker { return nil } +type CreateControllerLedRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *workers.Worker `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateControllerLedRequest) Reset() { + *x = CreateControllerLedRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateControllerLedRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateControllerLedRequest) ProtoMessage() {} + +func (x *CreateControllerLedRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateControllerLedRequest.ProtoReflect.Descriptor instead. +func (*CreateControllerLedRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{6} +} + +func (x *CreateControllerLedRequest) GetItem() *workers.Worker { + if x != nil { + return x.Item + } + return nil +} + +type CreateControllerLedResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty" class:"public"` // @gotags: `class:"public"` + Item *workers.Worker `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateControllerLedResponse) Reset() { + *x = CreateControllerLedResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateControllerLedResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateControllerLedResponse) ProtoMessage() {} + +func (x *CreateControllerLedResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateControllerLedResponse.ProtoReflect.Descriptor instead. +func (*CreateControllerLedResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{7} +} + +func (x *CreateControllerLedResponse) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *CreateControllerLedResponse) GetItem() *workers.Worker { + if x != nil { + return x.Item + } + return nil +} + type UpdateWorkerRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -344,7 +446,7 @@ type UpdateWorkerRequest struct { func (x *UpdateWorkerRequest) Reset() { *x = UpdateWorkerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[6] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -357,7 +459,7 @@ func (x *UpdateWorkerRequest) String() string { func (*UpdateWorkerRequest) ProtoMessage() {} func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[6] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -370,7 +472,7 @@ func (x *UpdateWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerRequest.ProtoReflect.Descriptor instead. func (*UpdateWorkerRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{6} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{8} } func (x *UpdateWorkerRequest) GetId() string { @@ -405,7 +507,7 @@ type UpdateWorkerResponse struct { func (x *UpdateWorkerResponse) Reset() { *x = UpdateWorkerResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[7] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -418,7 +520,7 @@ func (x *UpdateWorkerResponse) String() string { func (*UpdateWorkerResponse) ProtoMessage() {} func (x *UpdateWorkerResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[7] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -431,7 +533,7 @@ func (x *UpdateWorkerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateWorkerResponse.ProtoReflect.Descriptor instead. func (*UpdateWorkerResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{7} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{9} } func (x *UpdateWorkerResponse) GetItem() *workers.Worker { @@ -452,7 +554,7 @@ type DeleteWorkerRequest struct { func (x *DeleteWorkerRequest) Reset() { *x = DeleteWorkerRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[8] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -465,7 +567,7 @@ func (x *DeleteWorkerRequest) String() string { func (*DeleteWorkerRequest) ProtoMessage() {} func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[8] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -478,7 +580,7 @@ func (x *DeleteWorkerRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerRequest.ProtoReflect.Descriptor instead. func (*DeleteWorkerRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{8} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{10} } func (x *DeleteWorkerRequest) GetId() string { @@ -497,7 +599,7 @@ type DeleteWorkerResponse struct { func (x *DeleteWorkerResponse) Reset() { *x = DeleteWorkerResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[9] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -510,7 +612,7 @@ func (x *DeleteWorkerResponse) String() string { func (*DeleteWorkerResponse) ProtoMessage() {} func (x *DeleteWorkerResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[9] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -523,7 +625,7 @@ func (x *DeleteWorkerResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteWorkerResponse.ProtoReflect.Descriptor instead. func (*DeleteWorkerResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{9} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{11} } type AddWorkerTagsRequest struct { @@ -541,7 +643,7 @@ type AddWorkerTagsRequest struct { func (x *AddWorkerTagsRequest) Reset() { *x = AddWorkerTagsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[10] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -554,7 +656,7 @@ func (x *AddWorkerTagsRequest) String() string { func (*AddWorkerTagsRequest) ProtoMessage() {} func (x *AddWorkerTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[10] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -567,7 +669,7 @@ func (x *AddWorkerTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkerTagsRequest.ProtoReflect.Descriptor instead. func (*AddWorkerTagsRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{10} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{12} } func (x *AddWorkerTagsRequest) GetId() string { @@ -602,7 +704,7 @@ type AddWorkerTagsResponse struct { func (x *AddWorkerTagsResponse) Reset() { *x = AddWorkerTagsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[11] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -615,7 +717,7 @@ func (x *AddWorkerTagsResponse) String() string { func (*AddWorkerTagsResponse) ProtoMessage() {} func (x *AddWorkerTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[11] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -628,7 +730,7 @@ func (x *AddWorkerTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AddWorkerTagsResponse.ProtoReflect.Descriptor instead. func (*AddWorkerTagsResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{11} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{13} } func (x *AddWorkerTagsResponse) GetItem() *workers.Worker { @@ -653,7 +755,7 @@ type SetWorkerTagsRequest struct { func (x *SetWorkerTagsRequest) Reset() { *x = SetWorkerTagsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[12] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -666,7 +768,7 @@ func (x *SetWorkerTagsRequest) String() string { func (*SetWorkerTagsRequest) ProtoMessage() {} func (x *SetWorkerTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[12] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -679,7 +781,7 @@ func (x *SetWorkerTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerTagsRequest.ProtoReflect.Descriptor instead. func (*SetWorkerTagsRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{12} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{14} } func (x *SetWorkerTagsRequest) GetId() string { @@ -714,7 +816,7 @@ type SetWorkerTagsResponse struct { func (x *SetWorkerTagsResponse) Reset() { *x = SetWorkerTagsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[13] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -727,7 +829,7 @@ func (x *SetWorkerTagsResponse) String() string { func (*SetWorkerTagsResponse) ProtoMessage() {} func (x *SetWorkerTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[13] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -740,7 +842,7 @@ func (x *SetWorkerTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetWorkerTagsResponse.ProtoReflect.Descriptor instead. func (*SetWorkerTagsResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{13} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{15} } func (x *SetWorkerTagsResponse) GetItem() *workers.Worker { @@ -765,7 +867,7 @@ type RemoveWorkerTagsRequest struct { func (x *RemoveWorkerTagsRequest) Reset() { *x = RemoveWorkerTagsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[14] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -778,7 +880,7 @@ func (x *RemoveWorkerTagsRequest) String() string { func (*RemoveWorkerTagsRequest) ProtoMessage() {} func (x *RemoveWorkerTagsRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[14] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -791,7 +893,7 @@ func (x *RemoveWorkerTagsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkerTagsRequest.ProtoReflect.Descriptor instead. func (*RemoveWorkerTagsRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{14} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{16} } func (x *RemoveWorkerTagsRequest) GetId() string { @@ -826,7 +928,7 @@ type RemoveWorkerTagsResponse struct { func (x *RemoveWorkerTagsResponse) Reset() { *x = RemoveWorkerTagsResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[15] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -839,7 +941,7 @@ func (x *RemoveWorkerTagsResponse) String() string { func (*RemoveWorkerTagsResponse) ProtoMessage() {} func (x *RemoveWorkerTagsResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[15] + mi := &file_controller_api_services_v1_worker_service_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -852,7 +954,7 @@ func (x *RemoveWorkerTagsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RemoveWorkerTagsResponse.ProtoReflect.Descriptor instead. func (*RemoveWorkerTagsResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{15} + return file_controller_api_services_v1_worker_service_proto_rawDescGZIP(), []int{17} } func (x *RemoveWorkerTagsResponse) GetItem() *workers.Worker { @@ -914,191 +1016,218 @@ var file_controller_api_services_v1_worker_service_proto_rawDesc = []byte{ 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x22, 0xa4, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, + 0x6d, 0x22, 0x5d, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x70, 0x0a, 0x1b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x10, 0x0a, 0x03, 0x75, 0x72, 0x69, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, + 0x69, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0xa4, 0x01, 0x0a, 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x57, 0x0a, 0x14, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0x25, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0x3c, 0x0a, 0x0b, 0x75, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4d, 0x61, 0x73, 0x6b, 0x52, 0x0b, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x5f, 0x6d, 0x61, 0x73, 0x6b, 0x22, 0x57, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0xf3, 0x01, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, + 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x1a, + 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x22, 0x25, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x16, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0xf3, 0x01, 0x0a, 0x14, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x56, - 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x15, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0xf3, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, - 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, - 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x56, - 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, - 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x22, 0xf9, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, - 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, - 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, - 0x74, 0x61, 0x67, 0x73, 0x1a, 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, 0x18, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xfd, 0x0b, 0x0a, 0x0d, 0x57, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa2, 0x01, 0x0a, 0x09, - 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x6d, 0x22, 0xf3, 0x01, 0x0a, 0x14, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, + 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x59, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, + 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x1a, + 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x58, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x22, 0xf9, 0x01, 0x0a, 0x17, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x5c, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, + 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x40, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, 0x65, 0x74, - 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, - 0x12, 0x9a, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, - 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, - 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x2a, 0x92, 0x41, 0x14, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, - 0x6c, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, - 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0xca, 0x01, - 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x65, - 0x64, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, - 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, 0x41, 0x1a, 0x12, - 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, - 0x65, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, - 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x3a, 0x63, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x3a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x6c, 0x65, 0x64, 0x3a, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xad, 0x01, 0x0a, 0x0c, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, - 0x92, 0x41, 0x13, 0x12, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32, 0x10, 0x2f, 0x76, - 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, - 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xa1, 0x01, 0x0a, 0x0c, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, - 0x92, 0x41, 0x13, 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, - 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd0, - 0x01, 0x0a, 0x0d, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, - 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, - 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, - 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x41, + 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, + 0x5f, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x5b, 0x0a, + 0x18, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xda, 0x0d, 0x0a, 0x0d, 0x57, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa2, 0x01, 0x0a, + 0x09, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, 0x65, + 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x12, 0x9a, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x73, 0x12, 0x2e, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x2a, 0x92, 0x41, 0x14, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, + 0x6c, 0x6c, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x0d, 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x12, 0xca, + 0x01, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, + 0x65, 0x64, 0x12, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, - 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x26, 0x12, 0x24, 0x41, 0x64, 0x64, 0x73, - 0x20, 0x61, 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x20, - 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, - 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, - 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x77, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, - 0x6d, 0x12, 0xd1, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, - 0x61, 0x67, 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x4c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x92, 0x41, 0x1a, + 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, + 0x6c, 0x65, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, + 0x22, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x3a, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x3a, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x6c, 0x65, 0x64, 0x3a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xda, 0x01, 0x0a, 0x13, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x4c, 0x65, 0x64, 0x12, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x4c, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x37, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, + 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x4c, 0x65, 0x64, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x52, 0x92, 0x41, 0x1a, 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2f, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x3a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x3a, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2d, 0x6c, 0x65, 0x64, 0x3a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xad, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x92, 0x41, + 0x13, 0x12, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32, 0x10, 0x2f, 0x76, 0x31, 0x2f, + 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xa1, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x6c, + 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x2f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x92, 0x41, + 0x13, 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, 0x2f, + 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x12, 0xd0, 0x01, 0x0a, + 0x0d, 0x41, 0x64, 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x30, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, 0x64, 0x57, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, + 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x64, + 0x64, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x5a, 0x92, 0x41, 0x26, 0x12, 0x24, 0x41, 0x64, 0x64, 0x73, 0x20, 0x61, + 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, + 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, + 0xd1, 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, + 0x73, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x65, 0x74, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x92, 0x41, 0x27, 0x12, 0x25, 0x53, - 0x65, 0x74, 0x73, 0x20, 0x61, 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x66, 0x6f, 0x72, - 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, - 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, - 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x62, - 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xe1, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, - 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, - 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, - 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x62, 0x92, 0x41, 0x2b, 0x12, 0x29, 0x52, 0x65, 0x6d, 0x6f, - 0x76, 0x65, 0x73, 0x20, 0x61, 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x66, 0x72, 0x6f, - 0x6d, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, - 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x22, 0x23, 0x2f, 0x76, 0x31, - 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, - 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x42, 0x4d, 0x5a, 0x4b, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, - 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x5b, 0x92, 0x41, 0x27, 0x12, 0x25, 0x53, 0x65, 0x74, + 0x73, 0x20, 0x61, 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, + 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, 0x72, 0x6b, 0x65, + 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x22, 0x20, 0x2f, 0x76, 0x31, 0x2f, 0x77, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x12, 0xe1, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, + 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x54, 0x61, 0x67, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x62, 0x92, 0x41, 0x2b, 0x12, 0x29, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x73, 0x20, 0x61, 0x70, 0x69, 0x20, 0x74, 0x61, 0x67, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, + 0x61, 0x6e, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x57, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2e, 0x22, 0x23, 0x2f, 0x76, 0x31, 0x2f, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x2d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2d, 0x74, 0x61, 0x67, 0x73, 0x3a, 0x01, + 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x42, 0x4d, 0x5a, 0x4b, 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, 0x67, 0x65, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x3b, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1113,69 +1242,75 @@ func file_controller_api_services_v1_worker_service_proto_rawDescGZIP() []byte { return file_controller_api_services_v1_worker_service_proto_rawDescData } -var file_controller_api_services_v1_worker_service_proto_msgTypes = make([]protoimpl.MessageInfo, 19) +var file_controller_api_services_v1_worker_service_proto_msgTypes = make([]protoimpl.MessageInfo, 21) var file_controller_api_services_v1_worker_service_proto_goTypes = []interface{}{ - (*GetWorkerRequest)(nil), // 0: controller.api.services.v1.GetWorkerRequest - (*GetWorkerResponse)(nil), // 1: controller.api.services.v1.GetWorkerResponse - (*ListWorkersRequest)(nil), // 2: controller.api.services.v1.ListWorkersRequest - (*ListWorkersResponse)(nil), // 3: controller.api.services.v1.ListWorkersResponse - (*CreateWorkerLedRequest)(nil), // 4: controller.api.services.v1.CreateWorkerLedRequest - (*CreateWorkerLedResponse)(nil), // 5: controller.api.services.v1.CreateWorkerLedResponse - (*UpdateWorkerRequest)(nil), // 6: controller.api.services.v1.UpdateWorkerRequest - (*UpdateWorkerResponse)(nil), // 7: controller.api.services.v1.UpdateWorkerResponse - (*DeleteWorkerRequest)(nil), // 8: controller.api.services.v1.DeleteWorkerRequest - (*DeleteWorkerResponse)(nil), // 9: controller.api.services.v1.DeleteWorkerResponse - (*AddWorkerTagsRequest)(nil), // 10: controller.api.services.v1.AddWorkerTagsRequest - (*AddWorkerTagsResponse)(nil), // 11: controller.api.services.v1.AddWorkerTagsResponse - (*SetWorkerTagsRequest)(nil), // 12: controller.api.services.v1.SetWorkerTagsRequest - (*SetWorkerTagsResponse)(nil), // 13: controller.api.services.v1.SetWorkerTagsResponse - (*RemoveWorkerTagsRequest)(nil), // 14: controller.api.services.v1.RemoveWorkerTagsRequest - (*RemoveWorkerTagsResponse)(nil), // 15: controller.api.services.v1.RemoveWorkerTagsResponse - nil, // 16: controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry - nil, // 17: controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry - nil, // 18: controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry - (*workers.Worker)(nil), // 19: controller.api.resources.workers.v1.Worker - (*fieldmaskpb.FieldMask)(nil), // 20: google.protobuf.FieldMask - (*structpb.ListValue)(nil), // 21: google.protobuf.ListValue + (*GetWorkerRequest)(nil), // 0: controller.api.services.v1.GetWorkerRequest + (*GetWorkerResponse)(nil), // 1: controller.api.services.v1.GetWorkerResponse + (*ListWorkersRequest)(nil), // 2: controller.api.services.v1.ListWorkersRequest + (*ListWorkersResponse)(nil), // 3: controller.api.services.v1.ListWorkersResponse + (*CreateWorkerLedRequest)(nil), // 4: controller.api.services.v1.CreateWorkerLedRequest + (*CreateWorkerLedResponse)(nil), // 5: controller.api.services.v1.CreateWorkerLedResponse + (*CreateControllerLedRequest)(nil), // 6: controller.api.services.v1.CreateControllerLedRequest + (*CreateControllerLedResponse)(nil), // 7: controller.api.services.v1.CreateControllerLedResponse + (*UpdateWorkerRequest)(nil), // 8: controller.api.services.v1.UpdateWorkerRequest + (*UpdateWorkerResponse)(nil), // 9: controller.api.services.v1.UpdateWorkerResponse + (*DeleteWorkerRequest)(nil), // 10: controller.api.services.v1.DeleteWorkerRequest + (*DeleteWorkerResponse)(nil), // 11: controller.api.services.v1.DeleteWorkerResponse + (*AddWorkerTagsRequest)(nil), // 12: controller.api.services.v1.AddWorkerTagsRequest + (*AddWorkerTagsResponse)(nil), // 13: controller.api.services.v1.AddWorkerTagsResponse + (*SetWorkerTagsRequest)(nil), // 14: controller.api.services.v1.SetWorkerTagsRequest + (*SetWorkerTagsResponse)(nil), // 15: controller.api.services.v1.SetWorkerTagsResponse + (*RemoveWorkerTagsRequest)(nil), // 16: controller.api.services.v1.RemoveWorkerTagsRequest + (*RemoveWorkerTagsResponse)(nil), // 17: controller.api.services.v1.RemoveWorkerTagsResponse + nil, // 18: controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry + nil, // 19: controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry + nil, // 20: controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry + (*workers.Worker)(nil), // 21: controller.api.resources.workers.v1.Worker + (*fieldmaskpb.FieldMask)(nil), // 22: google.protobuf.FieldMask + (*structpb.ListValue)(nil), // 23: google.protobuf.ListValue } var file_controller_api_services_v1_worker_service_proto_depIdxs = []int32{ - 19, // 0: controller.api.services.v1.GetWorkerResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 19, // 1: controller.api.services.v1.ListWorkersResponse.items:type_name -> controller.api.resources.workers.v1.Worker - 19, // 2: controller.api.services.v1.CreateWorkerLedRequest.item:type_name -> controller.api.resources.workers.v1.Worker - 19, // 3: controller.api.services.v1.CreateWorkerLedResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 19, // 4: controller.api.services.v1.UpdateWorkerRequest.item:type_name -> controller.api.resources.workers.v1.Worker - 20, // 5: controller.api.services.v1.UpdateWorkerRequest.update_mask:type_name -> google.protobuf.FieldMask - 19, // 6: controller.api.services.v1.UpdateWorkerResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 16, // 7: controller.api.services.v1.AddWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry - 19, // 8: controller.api.services.v1.AddWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 17, // 9: controller.api.services.v1.SetWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry - 19, // 10: controller.api.services.v1.SetWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 18, // 11: controller.api.services.v1.RemoveWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry - 19, // 12: controller.api.services.v1.RemoveWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker - 21, // 13: controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue - 21, // 14: controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue - 21, // 15: controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue - 0, // 16: controller.api.services.v1.WorkerService.GetWorker:input_type -> controller.api.services.v1.GetWorkerRequest - 2, // 17: controller.api.services.v1.WorkerService.ListWorkers:input_type -> controller.api.services.v1.ListWorkersRequest - 4, // 18: controller.api.services.v1.WorkerService.CreateWorkerLed:input_type -> controller.api.services.v1.CreateWorkerLedRequest - 6, // 19: controller.api.services.v1.WorkerService.UpdateWorker:input_type -> controller.api.services.v1.UpdateWorkerRequest - 8, // 20: controller.api.services.v1.WorkerService.DeleteWorker:input_type -> controller.api.services.v1.DeleteWorkerRequest - 10, // 21: controller.api.services.v1.WorkerService.AddWorkerTags:input_type -> controller.api.services.v1.AddWorkerTagsRequest - 12, // 22: controller.api.services.v1.WorkerService.SetWorkerTags:input_type -> controller.api.services.v1.SetWorkerTagsRequest - 14, // 23: controller.api.services.v1.WorkerService.RemoveWorkerTags:input_type -> controller.api.services.v1.RemoveWorkerTagsRequest - 1, // 24: controller.api.services.v1.WorkerService.GetWorker:output_type -> controller.api.services.v1.GetWorkerResponse - 3, // 25: controller.api.services.v1.WorkerService.ListWorkers:output_type -> controller.api.services.v1.ListWorkersResponse - 5, // 26: controller.api.services.v1.WorkerService.CreateWorkerLed:output_type -> controller.api.services.v1.CreateWorkerLedResponse - 7, // 27: controller.api.services.v1.WorkerService.UpdateWorker:output_type -> controller.api.services.v1.UpdateWorkerResponse - 9, // 28: controller.api.services.v1.WorkerService.DeleteWorker:output_type -> controller.api.services.v1.DeleteWorkerResponse - 11, // 29: controller.api.services.v1.WorkerService.AddWorkerTags:output_type -> controller.api.services.v1.AddWorkerTagsResponse - 13, // 30: controller.api.services.v1.WorkerService.SetWorkerTags:output_type -> controller.api.services.v1.SetWorkerTagsResponse - 15, // 31: controller.api.services.v1.WorkerService.RemoveWorkerTags:output_type -> controller.api.services.v1.RemoveWorkerTagsResponse - 24, // [24:32] is the sub-list for method output_type - 16, // [16:24] is the sub-list for method input_type - 16, // [16:16] is the sub-list for extension type_name - 16, // [16:16] is the sub-list for extension extendee - 0, // [0:16] is the sub-list for field type_name + 21, // 0: controller.api.services.v1.GetWorkerResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 21, // 1: controller.api.services.v1.ListWorkersResponse.items:type_name -> controller.api.resources.workers.v1.Worker + 21, // 2: controller.api.services.v1.CreateWorkerLedRequest.item:type_name -> controller.api.resources.workers.v1.Worker + 21, // 3: controller.api.services.v1.CreateWorkerLedResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 21, // 4: controller.api.services.v1.CreateControllerLedRequest.item:type_name -> controller.api.resources.workers.v1.Worker + 21, // 5: controller.api.services.v1.CreateControllerLedResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 21, // 6: controller.api.services.v1.UpdateWorkerRequest.item:type_name -> controller.api.resources.workers.v1.Worker + 22, // 7: controller.api.services.v1.UpdateWorkerRequest.update_mask:type_name -> google.protobuf.FieldMask + 21, // 8: controller.api.services.v1.UpdateWorkerResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 18, // 9: controller.api.services.v1.AddWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry + 21, // 10: controller.api.services.v1.AddWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 19, // 11: controller.api.services.v1.SetWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry + 21, // 12: controller.api.services.v1.SetWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 20, // 13: controller.api.services.v1.RemoveWorkerTagsRequest.api_tags:type_name -> controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry + 21, // 14: controller.api.services.v1.RemoveWorkerTagsResponse.item:type_name -> controller.api.resources.workers.v1.Worker + 23, // 15: controller.api.services.v1.AddWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue + 23, // 16: controller.api.services.v1.SetWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue + 23, // 17: controller.api.services.v1.RemoveWorkerTagsRequest.ApiTagsEntry.value:type_name -> google.protobuf.ListValue + 0, // 18: controller.api.services.v1.WorkerService.GetWorker:input_type -> controller.api.services.v1.GetWorkerRequest + 2, // 19: controller.api.services.v1.WorkerService.ListWorkers:input_type -> controller.api.services.v1.ListWorkersRequest + 4, // 20: controller.api.services.v1.WorkerService.CreateWorkerLed:input_type -> controller.api.services.v1.CreateWorkerLedRequest + 6, // 21: controller.api.services.v1.WorkerService.CreateControllerLed:input_type -> controller.api.services.v1.CreateControllerLedRequest + 8, // 22: controller.api.services.v1.WorkerService.UpdateWorker:input_type -> controller.api.services.v1.UpdateWorkerRequest + 10, // 23: controller.api.services.v1.WorkerService.DeleteWorker:input_type -> controller.api.services.v1.DeleteWorkerRequest + 12, // 24: controller.api.services.v1.WorkerService.AddWorkerTags:input_type -> controller.api.services.v1.AddWorkerTagsRequest + 14, // 25: controller.api.services.v1.WorkerService.SetWorkerTags:input_type -> controller.api.services.v1.SetWorkerTagsRequest + 16, // 26: controller.api.services.v1.WorkerService.RemoveWorkerTags:input_type -> controller.api.services.v1.RemoveWorkerTagsRequest + 1, // 27: controller.api.services.v1.WorkerService.GetWorker:output_type -> controller.api.services.v1.GetWorkerResponse + 3, // 28: controller.api.services.v1.WorkerService.ListWorkers:output_type -> controller.api.services.v1.ListWorkersResponse + 5, // 29: controller.api.services.v1.WorkerService.CreateWorkerLed:output_type -> controller.api.services.v1.CreateWorkerLedResponse + 7, // 30: controller.api.services.v1.WorkerService.CreateControllerLed:output_type -> controller.api.services.v1.CreateControllerLedResponse + 9, // 31: controller.api.services.v1.WorkerService.UpdateWorker:output_type -> controller.api.services.v1.UpdateWorkerResponse + 11, // 32: controller.api.services.v1.WorkerService.DeleteWorker:output_type -> controller.api.services.v1.DeleteWorkerResponse + 13, // 33: controller.api.services.v1.WorkerService.AddWorkerTags:output_type -> controller.api.services.v1.AddWorkerTagsResponse + 15, // 34: controller.api.services.v1.WorkerService.SetWorkerTags:output_type -> controller.api.services.v1.SetWorkerTagsResponse + 17, // 35: controller.api.services.v1.WorkerService.RemoveWorkerTags:output_type -> controller.api.services.v1.RemoveWorkerTagsResponse + 27, // [27:36] is the sub-list for method output_type + 18, // [18:27] is the sub-list for method input_type + 18, // [18:18] is the sub-list for extension type_name + 18, // [18:18] is the sub-list for extension extendee + 0, // [0:18] is the sub-list for field type_name } func init() { file_controller_api_services_v1_worker_service_proto_init() } @@ -1257,7 +1392,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateWorkerRequest); i { + switch v := v.(*CreateControllerLedRequest); i { case 0: return &v.state case 1: @@ -1269,7 +1404,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateWorkerResponse); i { + switch v := v.(*CreateControllerLedResponse); i { case 0: return &v.state case 1: @@ -1281,7 +1416,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteWorkerRequest); i { + switch v := v.(*UpdateWorkerRequest); i { case 0: return &v.state case 1: @@ -1293,7 +1428,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DeleteWorkerResponse); i { + switch v := v.(*UpdateWorkerResponse); i { case 0: return &v.state case 1: @@ -1305,7 +1440,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddWorkerTagsRequest); i { + switch v := v.(*DeleteWorkerRequest); i { case 0: return &v.state case 1: @@ -1317,7 +1452,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AddWorkerTagsResponse); i { + switch v := v.(*DeleteWorkerResponse); i { case 0: return &v.state case 1: @@ -1329,7 +1464,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetWorkerTagsRequest); i { + switch v := v.(*AddWorkerTagsRequest); i { case 0: return &v.state case 1: @@ -1341,7 +1476,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SetWorkerTagsResponse); i { + switch v := v.(*AddWorkerTagsResponse); i { case 0: return &v.state case 1: @@ -1353,7 +1488,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RemoveWorkerTagsRequest); i { + switch v := v.(*SetWorkerTagsRequest); i { case 0: return &v.state case 1: @@ -1365,6 +1500,30 @@ func file_controller_api_services_v1_worker_service_proto_init() { } } file_controller_api_services_v1_worker_service_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetWorkerTagsResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_worker_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveWorkerTagsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_worker_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*RemoveWorkerTagsResponse); i { case 0: return &v.state @@ -1383,7 +1542,7 @@ func file_controller_api_services_v1_worker_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_services_v1_worker_service_proto_rawDesc, NumEnums: 0, - NumMessages: 19, + NumMessages: 21, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/gen/controller/api/services/worker_service.pb.gw.go b/internal/gen/controller/api/services/worker_service.pb.gw.go index 956c91b06..024944c47 100644 --- a/internal/gen/controller/api/services/worker_service.pb.gw.go +++ b/internal/gen/controller/api/services/worker_service.pb.gw.go @@ -153,6 +153,40 @@ func local_request_WorkerService_CreateWorkerLed_0(ctx context.Context, marshale } +func request_WorkerService_CreateControllerLed_0(ctx context.Context, marshaler runtime.Marshaler, client WorkerServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateControllerLedRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.CreateControllerLed(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_WorkerService_CreateControllerLed_0(ctx context.Context, marshaler runtime.Marshaler, server WorkerServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateControllerLedRequest + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq.Item); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.CreateControllerLed(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_WorkerService_UpdateWorker_0 = &utilities.DoubleArray{Encoding: map[string]int{"item": 0, "id": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}} ) @@ -590,6 +624,31 @@ func RegisterWorkerServiceHandlerServer(ctx context.Context, mux *runtime.ServeM }) + mux.Handle("POST", pattern_WorkerService_CreateControllerLed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.WorkerService/CreateControllerLed", runtime.WithHTTPPathPattern("/v1/workers:create:controller-led")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_WorkerService_CreateControllerLed_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_WorkerService_CreateControllerLed_0(annotatedContext, mux, outboundMarshaler, w, req, response_WorkerService_CreateControllerLed_0{resp}, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("PATCH", pattern_WorkerService_UpdateWorker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -822,6 +881,28 @@ func RegisterWorkerServiceHandlerClient(ctx context.Context, mux *runtime.ServeM }) + mux.Handle("POST", pattern_WorkerService_CreateControllerLed_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.WorkerService/CreateControllerLed", runtime.WithHTTPPathPattern("/v1/workers:create:controller-led")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_WorkerService_CreateControllerLed_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_WorkerService_CreateControllerLed_0(annotatedContext, mux, outboundMarshaler, w, req, response_WorkerService_CreateControllerLed_0{resp}, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("PATCH", pattern_WorkerService_UpdateWorker_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -953,6 +1034,15 @@ func (m response_WorkerService_CreateWorkerLed_0) XXX_ResponseBody() interface{} return response.Item } +type response_WorkerService_CreateControllerLed_0 struct { + proto.Message +} + +func (m response_WorkerService_CreateControllerLed_0) XXX_ResponseBody() interface{} { + response := m.Message.(*CreateControllerLedResponse) + return response.Item +} + type response_WorkerService_UpdateWorker_0 struct { proto.Message } @@ -996,6 +1086,8 @@ var ( pattern_WorkerService_CreateWorkerLed_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "workers:create"}, "worker-led")) + pattern_WorkerService_CreateControllerLed_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "workers:create"}, "controller-led")) + pattern_WorkerService_UpdateWorker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "workers", "id"}, "")) pattern_WorkerService_DeleteWorker_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "workers", "id"}, "")) @@ -1014,6 +1106,8 @@ var ( forward_WorkerService_CreateWorkerLed_0 = runtime.ForwardResponseMessage + forward_WorkerService_CreateControllerLed_0 = runtime.ForwardResponseMessage + forward_WorkerService_UpdateWorker_0 = runtime.ForwardResponseMessage forward_WorkerService_DeleteWorker_0 = runtime.ForwardResponseMessage diff --git a/internal/gen/controller/api/services/worker_service_grpc.pb.go b/internal/gen/controller/api/services/worker_service_grpc.pb.go index 6bd22d501..99d383a9d 100644 --- a/internal/gen/controller/api/services/worker_service_grpc.pb.go +++ b/internal/gen/controller/api/services/worker_service_grpc.pb.go @@ -28,12 +28,20 @@ type WorkerServiceClient interface { // If the scope ID is missing, malformed, or reference a non existing scope, // an error is returned. ListWorkers(ctx context.Context, in *ListWorkersRequest, opts ...grpc.CallOption) (*ListWorkersResponse, error) - // CreateWorkerLed creates and stores a Worker in boundary. The provided - // request must include the Scope id in which the Worker will be created. - // If the Scope id is missing, malformed or references a non existing - // resource, an error is returned. If a name is provided that is in - // use in another Worker in the same scope, an error is returned. + // CreateWorkerLed creates and stores a Worker in Boundary. The provided + // request must include the Scope ID in which the Worker will be created. If + // the Scope ID is missing, malformed or references a non existing resource, + // an error is returned. If a name is provided that is in use in another + // Worker in the same scope, an error is returned. CreateWorkerLed(ctx context.Context, in *CreateWorkerLedRequest, opts ...grpc.CallOption) (*CreateWorkerLedResponse, error) + // CreateControllerLed creates and stores a Worker in Boundary and returns an + // activation token that can be used by a worker binary to claim the created + // Worker's identity. The provided request must include the Scope ID in which + // the Worker will be created. If the Scope ID is missing, malformed or + // references a non existing resource, an error is returned. If a name is + // provided that is in use in another Worker in the same scope, an error is + // returned. + CreateControllerLed(ctx context.Context, in *CreateControllerLedRequest, opts ...grpc.CallOption) (*CreateControllerLedResponse, error) // UpdateWorker updates an existing Worker in boundary. The provided // Worker must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset @@ -93,6 +101,15 @@ func (c *workerServiceClient) CreateWorkerLed(ctx context.Context, in *CreateWor return out, nil } +func (c *workerServiceClient) CreateControllerLed(ctx context.Context, in *CreateControllerLedRequest, opts ...grpc.CallOption) (*CreateControllerLedResponse, error) { + out := new(CreateControllerLedResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.WorkerService/CreateControllerLed", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *workerServiceClient) UpdateWorker(ctx context.Context, in *UpdateWorkerRequest, opts ...grpc.CallOption) (*UpdateWorkerResponse, error) { out := new(UpdateWorkerResponse) err := c.cc.Invoke(ctx, "/controller.api.services.v1.WorkerService/UpdateWorker", in, out, opts...) @@ -152,12 +169,20 @@ type WorkerServiceServer interface { // If the scope ID is missing, malformed, or reference a non existing scope, // an error is returned. ListWorkers(context.Context, *ListWorkersRequest) (*ListWorkersResponse, error) - // CreateWorkerLed creates and stores a Worker in boundary. The provided - // request must include the Scope id in which the Worker will be created. - // If the Scope id is missing, malformed or references a non existing - // resource, an error is returned. If a name is provided that is in - // use in another Worker in the same scope, an error is returned. + // CreateWorkerLed creates and stores a Worker in Boundary. The provided + // request must include the Scope ID in which the Worker will be created. If + // the Scope ID is missing, malformed or references a non existing resource, + // an error is returned. If a name is provided that is in use in another + // Worker in the same scope, an error is returned. CreateWorkerLed(context.Context, *CreateWorkerLedRequest) (*CreateWorkerLedResponse, error) + // CreateControllerLed creates and stores a Worker in Boundary and returns an + // activation token that can be used by a worker binary to claim the created + // Worker's identity. The provided request must include the Scope ID in which + // the Worker will be created. If the Scope ID is missing, malformed or + // references a non existing resource, an error is returned. If a name is + // provided that is in use in another Worker in the same scope, an error is + // returned. + CreateControllerLed(context.Context, *CreateControllerLedRequest) (*CreateControllerLedResponse, error) // UpdateWorker updates an existing Worker in boundary. The provided // Worker must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset @@ -196,6 +221,9 @@ func (UnimplementedWorkerServiceServer) ListWorkers(context.Context, *ListWorker func (UnimplementedWorkerServiceServer) CreateWorkerLed(context.Context, *CreateWorkerLedRequest) (*CreateWorkerLedResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateWorkerLed not implemented") } +func (UnimplementedWorkerServiceServer) CreateControllerLed(context.Context, *CreateControllerLedRequest) (*CreateControllerLedResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateControllerLed not implemented") +} func (UnimplementedWorkerServiceServer) UpdateWorker(context.Context, *UpdateWorkerRequest) (*UpdateWorkerResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateWorker not implemented") } @@ -278,6 +306,24 @@ func _WorkerService_CreateWorkerLed_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _WorkerService_CreateControllerLed_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateControllerLedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WorkerServiceServer).CreateControllerLed(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.WorkerService/CreateControllerLed", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WorkerServiceServer).CreateControllerLed(ctx, req.(*CreateControllerLedRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WorkerService_UpdateWorker_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateWorkerRequest) if err := dec(in); err != nil { @@ -387,6 +433,10 @@ var WorkerService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreateWorkerLed", Handler: _WorkerService_CreateWorkerLed_Handler, }, + { + MethodName: "CreateControllerLed", + Handler: _WorkerService_CreateControllerLed_Handler, + }, { MethodName: "UpdateWorker", Handler: _WorkerService_UpdateWorker_Handler, diff --git a/internal/perms/acl_test.go b/internal/perms/acl_test.go index 14088b423..8e3e31a6f 100644 --- a/internal/perms/acl_test.go +++ b/internal/perms/acl_test.go @@ -427,7 +427,7 @@ func Test_AnonRestrictions(t *testing.T) { if i == resource.Controller || i == resource.Worker { continue } - for j := action.Type(1); j <= action.RemoveHostSources; j++ { + for j := action.Type(1); j <= action.CreateControllerLed; j++ { res := Resource{ ScopeId: scope.Global.String(), Id: "foobar", diff --git a/internal/proto/controller/api/resources/workers/v1/worker.proto b/internal/proto/controller/api/resources/workers/v1/worker.proto index be305c64c..9e169fff8 100644 --- a/internal/proto/controller/api/resources/workers/v1/worker.proto +++ b/internal/proto/controller/api/resources/workers/v1/worker.proto @@ -72,6 +72,10 @@ message Worker { // https://github.com/hashicorp/nodeenrollment google.protobuf.StringValue worker_generated_auth_token = 150 [json_name = "worker_generated_auth_token"]; // @gotags: `class:"public"` + // Output only. An activation token that can be given to a worker to correlate + // it to the created resource. + google.protobuf.StringValue controller_generated_activation_token = 151 [json_name = "controller_generated_activation_token"]; // @gotags: `class:"secret"` + // Output only. The number of connections that this worker is currently handling. google.protobuf.UInt32Value active_connection_count = 160 [json_name = "active_connection_count"]; // @gotags: `class:"public"` diff --git a/internal/proto/controller/api/services/v1/worker_service.proto b/internal/proto/controller/api/services/v1/worker_service.proto index 73c4ab181..421b53c8e 100644 --- a/internal/proto/controller/api/services/v1/worker_service.proto +++ b/internal/proto/controller/api/services/v1/worker_service.proto @@ -38,11 +38,11 @@ service WorkerService { }; } - // CreateWorkerLed creates and stores a Worker in boundary. The provided - // request must include the Scope id in which the Worker will be created. - // If the Scope id is missing, malformed or references a non existing - // resource, an error is returned. If a name is provided that is in - // use in another Worker in the same scope, an error is returned. + // CreateWorkerLed creates and stores a Worker in Boundary. The provided + // request must include the Scope ID in which the Worker will be created. If + // the Scope ID is missing, malformed or references a non existing resource, + // an error is returned. If a name is provided that is in use in another + // Worker in the same scope, an error is returned. rpc CreateWorkerLed(CreateWorkerLedRequest) returns (CreateWorkerLedResponse) { option (google.api.http) = { post: "/v1/workers:create:worker-led" @@ -54,6 +54,24 @@ service WorkerService { }; } + // CreateControllerLed creates and stores a Worker in Boundary and returns an + // activation token that can be used by a worker binary to claim the created + // Worker's identity. The provided request must include the Scope ID in which + // the Worker will be created. If the Scope ID is missing, malformed or + // references a non existing resource, an error is returned. If a name is + // provided that is in use in another Worker in the same scope, an error is + // returned. + rpc CreateControllerLed(CreateControllerLedRequest) returns (CreateControllerLedResponse) { + option (google.api.http) = { + post: "/v1/workers:create:controller-led" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Creates a single Worker." + }; + } + // UpdateWorker updates an existing Worker in boundary. The provided // Worker must not have any read only fields set. The update mask must be // included in the request and contain at least 1 mutable field. To unset @@ -152,6 +170,15 @@ message CreateWorkerLedResponse { resources.workers.v1.Worker item = 2; } +message CreateControllerLedRequest { + resources.workers.v1.Worker item = 1; +} + +message CreateControllerLedResponse { + string uri = 1; // @gotags: `class:"public"` + resources.workers.v1.Worker item = 2; +} + message UpdateWorkerRequest { string id = 1; // @gotags: `class:"public"` resources.workers.v1.Worker item = 2; 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 07d70ea3a..abb728c73 100644 --- a/internal/proto/controller/storage/servers/store/v1/worker_auth.proto +++ b/internal/proto/controller/storage/servers/store/v1/worker_auth.proto @@ -51,3 +51,29 @@ message WorkerCertBundle { // @inject_tag: `gorm:"not_null"` bytes cert_bundle = 30; } + +// WorkerAuthServerLedActivationToken contains all fields related to a +// WorkerAuthServerLedActivationToken resource +message WorkerAuthServerLedActivationToken { + // The worker_id of the worker that this activates + // @inject_tag: `gorm:"primary_key"` + string worker_id = 10; + + // The token identifier, which is used for lookup + // @inject_tag: `gorm:"not_null"` + string token_id = 15; + + // The creation time, encrypted to prevent tampering, as the time plus + // existence of the record allows authorization + // @inject_tag: `gorm:"not_null" wrapping:"ct,creation_time_data"` + bytes creation_time_encrypted = 20; + + // The plaintext bytes of the creation time, which are never stored. This is a + // marshaled timestamppb.Timestamp. + // @inject_tag: `gorm:"-" wrapping:"pt,creation_time_data"` + bytes creation_time = 21; + + // The key ID of the encrypting key + // @inject_tag: `gorm:"not_null"` + string key_id = 40; +} diff --git a/internal/server/options.go b/internal/server/options.go index 354caed81..7c869f66c 100644 --- a/internal/server/options.go +++ b/internal/server/options.go @@ -8,7 +8,7 @@ import ( ) // getOpts - iterate the inbound Options and return a struct -func getOpts(opt ...Option) options { +func GetOpts(opt ...Option) options { opts := getDefaultOptions() for _, o := range opt { o(&opts) @@ -21,26 +21,27 @@ type Option func(*options) // options = how options are represented type options struct { - withName string - withPublicId string - withDescription string - withAddress string - withLimit int - withLiveness time.Duration - withUpdateTags bool - withWorkerTags []*Tag - withWorkerKeyIdentifier string - withWorkerKeys WorkerKeys - withControllerEncryptionPrivateKey []byte - withKeyId string - withNonce []byte - withNewIdFunc func(context.Context) (string, error) - withFetchNodeCredentialsRequest *types.FetchNodeCredentialsRequest - withTestPkiWorkerAuthorized bool - withTestPkiWorkerKeyId *string - withWorkerType WorkerType - withRoot string - withStopAfter uint + withName string + withPublicId string + withDescription string + withAddress string + withLimit int + withLiveness time.Duration + withUpdateTags bool + withWorkerTags []*Tag + withWorkerKeyIdentifier string + withWorkerKeys WorkerKeys + withControllerEncryptionPrivateKey []byte + withKeyId string + withNonce []byte + withNewIdFunc func(context.Context) (string, error) + WithFetchNodeCredentialsRequest *types.FetchNodeCredentialsRequest + withTestPkiWorkerAuthorized bool + withTestPkiWorkerKeyId *string + withWorkerType WorkerType + withRoot string + withStopAfter uint + WithCreateControllerLedActivationToken bool } func getDefaultOptions() options { @@ -153,7 +154,7 @@ func WithNewIdFunc(fn func(context.Context) (string, error)) Option { // FetchNodeCredentialsRequest to be specified. func WithFetchNodeCredentialsRequest(req *types.FetchNodeCredentialsRequest) Option { return func(o *options) { - o.withFetchNodeCredentialsRequest = req + o.WithFetchNodeCredentialsRequest = req } } @@ -188,3 +189,10 @@ func WithStopAfter(stopAfter uint) Option { o.withStopAfter = stopAfter } } + +// WithCreateControllerLedActivationToken provides an optional stop after count +func WithCreateControllerLedActivationToken(with bool) Option { + return func(o *options) { + o.WithCreateControllerLedActivationToken = with + } +} diff --git a/internal/server/options_test.go b/internal/server/options_test.go index 77bf79446..8b566c813 100644 --- a/internal/server/options_test.go +++ b/internal/server/options_test.go @@ -14,7 +14,7 @@ import ( func Test_GetOpts(t *testing.T) { t.Parallel() t.Run("WithName", func(t *testing.T) { - opts := getOpts(WithName("test")) + opts := GetOpts(WithName("test")) testOpts := getDefaultOptions() testOpts.withName = "test" opts.withNewIdFunc = nil @@ -22,7 +22,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithPublicId", func(t *testing.T) { - opts := getOpts(WithPublicId("test")) + opts := GetOpts(WithPublicId("test")) testOpts := getDefaultOptions() testOpts.withPublicId = "test" opts.withNewIdFunc = nil @@ -30,7 +30,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithDescription", func(t *testing.T) { - opts := getOpts(WithDescription("test desc")) + opts := GetOpts(WithDescription("test desc")) testOpts := getDefaultOptions() testOpts.withDescription = "test desc" opts.withNewIdFunc = nil @@ -38,7 +38,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithLimit", func(t *testing.T) { - opts := getOpts(WithLimit(5)) + opts := GetOpts(WithLimit(5)) testOpts := getDefaultOptions() testOpts.withLimit = 5 opts.withNewIdFunc = nil @@ -46,7 +46,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithAddress", func(t *testing.T) { - opts := getOpts(WithAddress("test")) + opts := GetOpts(WithAddress("test")) testOpts := getDefaultOptions() testOpts.withAddress = "test" opts.withNewIdFunc = nil @@ -54,7 +54,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithLiveness", func(t *testing.T) { - opts := getOpts(WithLiveness(time.Hour)) + opts := GetOpts(WithLiveness(time.Hour)) testOpts := getDefaultOptions() testOpts.withLiveness = time.Hour opts.withNewIdFunc = nil @@ -62,7 +62,7 @@ func Test_GetOpts(t *testing.T) { assert.Equal(t, opts, testOpts) }) t.Run("WithUpdateTags", func(t *testing.T) { - opts := getOpts(WithUpdateTags(true)) + opts := GetOpts(WithUpdateTags(true)) testOpts := getDefaultOptions() testOpts.withUpdateTags = true opts.withNewIdFunc = nil @@ -74,7 +74,7 @@ func Test_GetOpts(t *testing.T) { {Key: "key1", Value: "val1"}, {Key: "key2", Value: "val2"}, } - opts := getOpts(WithWorkerTags(tags...)) + opts := GetOpts(WithWorkerTags(tags...)) testOpts := getDefaultOptions() testOpts.withWorkerTags = tags opts.withNewIdFunc = nil @@ -90,7 +90,7 @@ func Test_GetOpts(t *testing.T) { workerSigningPubKey: populateBytes(20), } - opts := getOpts(WithWorkerKeys(keys)) + opts := GetOpts(WithWorkerKeys(keys)) testOpts = getDefaultOptions() testOpts.withWorkerKeys = keys opts.withNewIdFunc = nil @@ -103,7 +103,7 @@ func Test_GetOpts(t *testing.T) { key := populateBytes(20) - opts := getOpts(WithControllerEncryptionPrivateKey(key)) + opts := GetOpts(WithControllerEncryptionPrivateKey(key)) testOpts = getDefaultOptions() testOpts.withControllerEncryptionPrivateKey = key opts.withNewIdFunc = nil @@ -114,7 +114,7 @@ func Test_GetOpts(t *testing.T) { assert := assert.New(t) testOpts := getDefaultOptions() - opts := getOpts(WithKeyId("hi i'm another key id")) + opts := GetOpts(WithKeyId("hi i'm another key id")) testOpts = getDefaultOptions() testOpts.withKeyId = "hi i'm another key id" opts.withNewIdFunc = nil @@ -127,7 +127,7 @@ func Test_GetOpts(t *testing.T) { nonce := populateBytes(20) - opts := getOpts(WithNonce(nonce)) + opts := GetOpts(WithNonce(nonce)) testOpts = getDefaultOptions() testOpts.withNonce = nonce opts.withNewIdFunc = nil @@ -137,7 +137,7 @@ func Test_GetOpts(t *testing.T) { t.Run("WithNewIdFunc", func(t *testing.T) { assert := assert.New(t) testFn := func(context.Context) (string, error) { return "", nil } - opts := getOpts(WithNewIdFunc(testFn)) + opts := GetOpts(WithNewIdFunc(testFn)) testOpts := getDefaultOptions() testOpts.withNewIdFunc = testFn assert.Equal( @@ -150,7 +150,7 @@ func Test_GetOpts(t *testing.T) { testOpts := getDefaultOptions() var keyId string - opts := getOpts(WithTestPkiWorkerAuthorizedKeyId(&keyId)) + opts := GetOpts(WithTestPkiWorkerAuthorizedKeyId(&keyId)) testOpts = getDefaultOptions() testOpts.withTestPkiWorkerAuthorized = true testOpts.withTestPkiWorkerKeyId = &keyId @@ -161,19 +161,25 @@ func Test_GetOpts(t *testing.T) { t.Run("WithWorkerType", func(t *testing.T) { opts := getDefaultOptions() assert.Empty(t, opts.withWorkerType) - opts = getOpts(WithWorkerType(KmsWorkerType)) + opts = GetOpts(WithWorkerType(KmsWorkerType)) assert.Equal(t, KmsWorkerType, opts.withWorkerType) }) t.Run("WithRoot", func(t *testing.T) { opts := getDefaultOptions() assert.Empty(t, opts.withRoot) - opts = getOpts(WithRoot("a")) + opts = GetOpts(WithRoot("a")) assert.Equal(t, "a", opts.withRoot) }) t.Run("WithStopAfter", func(t *testing.T) { opts := getDefaultOptions() assert.Empty(t, opts.withStopAfter) - opts = getOpts(WithStopAfter(10)) + opts = GetOpts(WithStopAfter(10)) assert.Equal(t, uint(10), opts.withStopAfter) }) + t.Run("WithCreateControllerLedActivationToken", func(t *testing.T) { + opts := getDefaultOptions() + assert.False(t, opts.WithCreateControllerLedActivationToken) + opts = GetOpts(WithCreateControllerLedActivationToken(true)) + assert.True(t, opts.WithCreateControllerLedActivationToken) + }) } diff --git a/internal/server/repository.go b/internal/server/repository.go index 4742e92e4..a7df5afff 100644 --- a/internal/server/repository.go +++ b/internal/server/repository.go @@ -44,7 +44,7 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo return nil, errors.NewDeprecated(errors.InvalidParameter, op, "nil kms") } - opts := getOpts(opt...) + opts := GetOpts(opt...) if opts.withLimit == 0 { // zero signals the boundary defaults should be used. opts.withLimit = db.DefaultLimit diff --git a/internal/server/repository_controller.go b/internal/server/repository_controller.go index 65bb810b1..3cc5b45c9 100644 --- a/internal/server/repository_controller.go +++ b/internal/server/repository_controller.go @@ -17,7 +17,7 @@ func (r *Repository) ListControllers(ctx context.Context, opt ...Option) ([]*sto // WithLimit option or the repo defaultLimit. It accepts a reader, allowing it // to be used within a transaction or without. func (r *Repository) listControllersWithReader(ctx context.Context, reader db.Reader, opt ...Option) ([]*store.Controller, error) { - opts := getOpts(opt...) + opts := GetOpts(opt...) liveness := opts.withLiveness if liveness == 0 { liveness = DefaultLiveness diff --git a/internal/server/repository_worker.go b/internal/server/repository_worker.go index ea11a6cb3..daffcd8f6 100644 --- a/internal/server/repository_worker.go +++ b/internal/server/repository_worker.go @@ -11,10 +11,12 @@ import ( "github.com/hashicorp/boundary/internal/server/store" "github.com/hashicorp/boundary/internal/types/scope" "github.com/hashicorp/go-dbw" - wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/nodeenrollment" "github.com/hashicorp/nodeenrollment/registration" + "github.com/hashicorp/nodeenrollment/types" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" ) // DeleteWorker will delete a worker from the repository. @@ -170,7 +172,7 @@ func (r *Repository) ListWorkers(ctx context.Context, scopeIds []string, opt ... return nil, errors.New(ctx, errors.InvalidParameter, op, "no scope ids set") } - opts := getOpts(opt...) + opts := GetOpts(opt...) liveness := opts.withLiveness if liveness == 0 { liveness = DefaultLiveness @@ -233,7 +235,7 @@ func (r *Repository) ListWorkers(ctx context.Context, scopeIds []string, opt ... func (r *Repository) UpsertWorkerStatus(ctx context.Context, worker *Worker, opt ...Option) (*Worker, error) { const op = "server.UpsertWorkerStatus" - opts := getOpts(opt...) + opts := GetOpts(opt...) switch { case worker == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "worker is nil") @@ -492,10 +494,15 @@ 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: WithFetchNodeCredentialsRequest and WithNewIdFunc (this -// option is likely only useful for tests) +// Options supported: WithNewIdFunc (this option is likely only useful for +// tests), WithFetchNodeCredentialsRequest, +// WithCreateControllerLedActivationToken. The latter two are mutually +// exclusive. func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Option) (*Worker, error) { const op = "server.CreateWorker" + + opts := GetOpts(opt...) + switch { case worker == nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "missing worker") @@ -509,9 +516,10 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op return nil, errors.New(ctx, errors.InvalidParameter, op, "address is not empty") case worker.LastStatusTime != nil: return nil, errors.New(ctx, errors.InvalidParameter, op, "last status time is not nil") + case opts.WithFetchNodeCredentialsRequest != nil && opts.WithCreateControllerLedActivationToken: + return nil, errors.New(ctx, errors.InvalidParameter, op, "fetch node credentials request and controller led activation token option cannot both be set") } - opts := getOpts(opt...) var err error if worker.PublicId, err = opts.withNewIdFunc(ctx); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to generate worker id")) @@ -523,14 +531,11 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error generating state struct", err)) } - 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) - } + + databaseWrapper, err := r.kms.GetWrapper(context.Background(), worker.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper")) } var returnedWorker *Worker @@ -547,12 +552,14 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op ); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker")) } - if opts.withFetchNodeCredentialsRequest != nil { + + switch { + case opts.WithFetchNodeCredentialsRequest != nil: workerAuthRepo, err = NewRepositoryStorage(ctx, read, w, 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)) + 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")) } @@ -560,7 +567,35 @@ func (r *Repository) CreateWorker(ctx context.Context, worker *Worker, opt ...Op if err := StoreNodeInformationTx(ctx, w, databaseWrapper, nodeInfo); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to store node information")) } + + case opts.WithCreateControllerLedActivationToken: + tokenId, activationToken, err := registration.CreateServerLedActivationToken(ctx, nil, &types.ServerLedRegistrationRequest{}, + nodeenrollment.WithSkipStorage(true), + nodeenrollment.WithState(state)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create controller-led activation token")) + } + creationTime := timestamppb.Now() + creationTimeBytes, err := proto.Marshal(creationTime) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to marshal timestamp for activation token")) + } + activationTokenEntry, err := newWorkerAuthServerLedActivationToken(ctx, worker.PublicId, tokenId, creationTimeBytes) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in-memory activation token")) + } + if err := activationTokenEntry.encrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(ctx, err, op) + } + if err := w.Create( + ctx, + activationTokenEntry, + ); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create worker activation token in storage")) + } + returnedWorker.ControllerGeneratedActivationToken = activationToken } + return nil }, ); err != nil { @@ -590,10 +625,7 @@ func (r *Repository) AddWorkerTags(ctx context.Context, workerId string, workerV return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("no worker found with public id %s", workerId)) } - newTags := worker.apiTags - for _, t := range tags { - newTags = append(newTags, t) - } + newTags := append(worker.apiTags, tags...) _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { worker := worker.clone() worker.PublicId = workerId diff --git a/internal/server/repository_worker_test.go b/internal/server/repository_worker_test.go index 8949076fa..eb125d4e7 100644 --- a/internal/server/repository_worker_test.go +++ b/internal/server/repository_worker_test.go @@ -2,6 +2,7 @@ package server_test import ( "context" + "crypto/rand" "fmt" "strings" "testing" @@ -143,6 +144,7 @@ func TestLookupWorkerIdByKeyId(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) repo, err := server.NewRepository(rw, rw, kmsCache) require.NoError(t, err) ctx := context.Background() @@ -252,6 +254,7 @@ func TestUpsertWorkerStatus(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) repo, err := server.NewRepository(rw, rw, kmsCache) require.NoError(t, err) @@ -496,9 +499,11 @@ func TestListWorkers(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) - kms := kms.TestKms(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) + const testLimit = 10 - repo, err := server.NewRepository(rw, rw, kms, server.WithLimit(testLimit)) + repo, err := server.NewRepository(rw, rw, kmsCache, server.WithLimit(testLimit)) require.NoError(t, err) ctx := context.Background() @@ -696,7 +701,6 @@ func TestRepository_CreateWorker(t *testing.T) { name string setup func() *server.Worker repo *server.Repository - fetchReq *types.FetchNodeCredentialsRequest reader db.Reader opt []server.Option wantErr bool @@ -812,19 +816,21 @@ func TestRepository_CreateWorker(t *testing.T) { return w }, reader: rw, - fetchReq: func() *types.FetchNodeCredentialsRequest { - // This happens on the worker - fileStorage, err := file.New(testCtx) - require.NoError(t, err) - defer fileStorage.Cleanup() + opt: []server.Option{server.WithFetchNodeCredentialsRequest( + func() *types.FetchNodeCredentialsRequest { + // This happens on the worker + fileStorage, err := file.New(testCtx) + require.NoError(t, err) + defer fileStorage.Cleanup() - nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage) - require.NoError(t, err) - // Create request using worker id - fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx) - require.NoError(t, err) - return fetchReq - }(), + nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage) + require.NoError(t, err) + // Create request using worker id + fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx) + require.NoError(t, err) + return fetchReq + }(), + )}, repo: func() *server.Repository { mockConn, mock := db.TestSetupWithMock(t) mock.ExpectQuery(`SELECT`).WillReturnRows(sqlmock.NewRows([]string{"version", "create_time"}).AddRow(migrations.Version, time.Now())) @@ -844,7 +850,7 @@ func TestRepository_CreateWorker(t *testing.T) { return w }, reader: rw, - fetchReq: &types.FetchNodeCredentialsRequest{}, + opt: []server.Option{server.WithFetchNodeCredentialsRequest(&types.FetchNodeCredentialsRequest{})}, repo: testRepo, wantErr: true, wantErrContains: "unable to authorize node", @@ -862,7 +868,7 @@ func TestRepository_CreateWorker(t *testing.T) { wantErrIs: errors.NotUnique, }, { - name: "success", + name: "success-no-options", setup: func() *server.Worker { w := server.NewWorker(scope.Global.String()) w.Name = "success" @@ -892,29 +898,40 @@ func TestRepository_CreateWorker(t *testing.T) { return w }, reader: rw, - fetchReq: func() *types.FetchNodeCredentialsRequest { - // This happens on the worker - fileStorage, err := file.New(testCtx) - require.NoError(t, err) - defer fileStorage.Cleanup() + opt: []server.Option{server.WithFetchNodeCredentialsRequest( + func() *types.FetchNodeCredentialsRequest { + // This happens on the worker + fileStorage, err := file.New(testCtx) + require.NoError(t, err) + defer fileStorage.Cleanup() - nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage) - require.NoError(t, err) - // Create request using worker id - fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx) - require.NoError(t, err) - return fetchReq - }(), + nodeCreds, err := types.NewNodeCredentials(testCtx, fileStorage) + require.NoError(t, err) + // Create request using worker id + fetchReq, err := nodeCreds.CreateFetchNodeCredentialsRequest(testCtx) + require.NoError(t, err) + return fetchReq + }(), + )}, repo: testRepo, }, + { + name: "success-with-controller-activation-token", + setup: func() *server.Worker { + w := server.NewWorker(scope.Global.String()) + w.Name = "success-with-controller-activation-token" + return w + }, + reader: rw, + opt: []server.Option{server.WithCreateControllerLedActivationToken(true)}, + repo: testRepo, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) testWorker := tc.setup() - tc.opt = append(tc.opt, server.WithFetchNodeCredentialsRequest(tc.fetchReq)) - got, err := tc.repo.CreateWorker(testCtx, testWorker, tc.opt...) if tc.wantErr { require.Error(err) @@ -942,12 +959,19 @@ func TestRepository_CreateWorker(t *testing.T) { require.NoError(err) assert.Empty(cmp.Diff(got, found, protocmp.Transform())) - if tc.fetchReq != nil { + opts := server.GetOpts(tc.opt...) + if opts.WithFetchNodeCredentialsRequest != nil { worker := &server.WorkerAuth{ WorkerAuth: &store.WorkerAuth{}, } require.NoError(tc.reader.LookupWhere(testCtx, worker, "worker_id = ?", []any{found.PublicId})) } + if opts.WithCreateControllerLedActivationToken { + activationToken := &server.WorkerAuthServerLedActivationToken{ + WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{}, + } + require.NoError(tc.reader.LookupWhere(testCtx, activationToken, "worker_id = ?", []any{found.PublicId})) + } }) } } @@ -959,6 +983,7 @@ func TestRepository_UpdateWorker(t *testing.T) { rw := db.New(conn) wrapper := db.TestWrapper(t) kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) repo, err := server.NewRepository(rw, rw, kmsCache) require.NoError(t, err) diff --git a/internal/server/repository_workerauth.go b/internal/server/repository_workerauth.go index 9bdac5dfa..35117a870 100644 --- a/internal/server/repository_workerauth.go +++ b/internal/server/repository_workerauth.go @@ -9,6 +9,7 @@ import ( "github.com/fatih/structs" "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" @@ -85,6 +86,7 @@ func (r *WorkerAuthRepositoryStorage) Store(ctx context.Context, msg nodee.Messa }); err != nil { return errors.Wrap(ctx, err, op) } + case *types.RootCertificates: err := r.storeRootCertificates(ctx, t) if err != nil { @@ -343,6 +345,9 @@ func (r *WorkerAuthRepositoryStorage) Load(ctx context.Context, msg nodee.Messag case *types.NodeInformation: err = r.loadNodeInformation(ctx, t) + case *types.ServerLedActivationToken: + err = r.loadServerLedActivationToken(ctx, t) + case *types.RootCertificates: err = r.loadRootCertificates(ctx, t) @@ -416,6 +421,48 @@ func (r *WorkerAuthRepositoryStorage) loadNodeInformation(ctx context.Context, n return nil } +// Node information is loaded in two parts: +// * the workerAuth record +// * its certificate bundles +func (r *WorkerAuthRepositoryStorage) loadServerLedActivationToken(ctx context.Context, token *types.ServerLedActivationToken) error { + const op = "server.(WorkerAuthRepositoryStorage).loadServerLedActivationToken" + if token == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing ServerLedActivationToken") + } + + activationTokenEntry := allocWorkerAuthServerLedActivationToken() + + err := r.reader.LookupWhere(ctx, activationTokenEntry, "token_id = ?", []any{token.Id}) + if err != nil { + if errors.Is(err, dbw.ErrRecordNotFound) { + return nodee.ErrNotFound + } + return errors.Wrap(ctx, err, op) + } + + token.State, err = AttachWorkerIdToState(ctx, activationTokenEntry.WorkerId) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + // Decrypt marshaled creation time + databaseWrapper, err := r.kms.GetWrapper(ctx, scope.Global.String(), kms.KeyPurposeDatabase, kms.WithKeyId(activationTokenEntry.KeyId)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if err := activationTokenEntry.decrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(ctx, err, op) + } + token.CreationTimeMarshaled = activationTokenEntry.CreationTime + + token.CreationTime = new(timestamppb.Timestamp) + if err := proto.Unmarshal(token.CreationTimeMarshaled, token.CreationTime); err != nil { + return errors.Wrap(ctx, err, op) + } + + return nil +} + func (r *WorkerAuthRepositoryStorage) findCertBundles(ctx context.Context, workerKeyId string) ([]*types.CertificateBundle, error) { const op = "server.(WorkerAuthRepositoryStorage).findCertBundles" if workerKeyId == "" { @@ -537,9 +584,14 @@ func (r *WorkerAuthRepositoryStorage) Remove(ctx context.Context, msg nodee.Mess } // Determine type of message to remove - switch msg.(type) { + switch t := msg.(type) { case *types.NodeInformation: - err := r.removeNodeInformation(ctx, msg.GetId()) + err := r.removeNodeInformation(ctx, t) + if err != nil { + return errors.Wrap(ctx, err, op) + } + case *types.ServerLedActivationToken: + err := r.removeServerLedActivationToken(ctx, t) if err != nil { return errors.Wrap(ctx, err, op) } @@ -555,9 +607,12 @@ func (r *WorkerAuthRepositoryStorage) Remove(ctx context.Context, msg nodee.Mess return nil } -func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, id string) error { +func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, msg *types.NodeInformation) error { const op = "server.(WorkerAuthRepositoryStorage).removeNodeInformation" - if id == "" { + switch { + case msg == nil: + return errors.New(ctx, errors.InvalidParameter, op, "nil node information") + case msg.Id == "": return errors.New(ctx, errors.InvalidParameter, op, "empty id") } @@ -567,11 +622,11 @@ func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { var err error - _, err = w.Exec(ctx, deleteWorkerAuthQuery, []interface{}{sql.Named("worker_key_identifier", id)}) + _, err = w.Exec(ctx, deleteWorkerAuthQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)}) if err != nil { return errors.Wrap(ctx, err, op) } - _, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", id)}) + _, err = w.Exec(ctx, deleteWorkerCertBundlesQuery, []interface{}{sql.Named("worker_key_identifier", msg.Id)}) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete workerAuth")) } @@ -585,6 +640,49 @@ func (r *WorkerAuthRepositoryStorage) removeNodeInformation(ctx context.Context, return nil } +func (r *WorkerAuthRepositoryStorage) removeServerLedActivationToken(ctx context.Context, msg *types.ServerLedActivationToken) error { + const op = "server.(WorkerAuthRepositoryStorage).removeServerLedActivationToken" + switch { + case msg == nil: + return errors.New(ctx, errors.InvalidParameter, op, "nil node information") + case msg.Id == "": + return errors.New(ctx, errors.InvalidParameter, op, "empty id") + case msg.State == nil: + return errors.New(ctx, errors.InvalidParameter, op, "missing state") + } + + var workerInfo workerAuthWorkerId + if err := mapstructure.Decode(msg.State.AsMap(), &workerInfo); err != nil { + return errors.Wrap(ctx, err, op) + } + if workerInfo.WorkerId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "state missing worker id") + } + + actToken := allocWorkerAuthServerLedActivationToken() + actToken.WorkerId = workerInfo.WorkerId + _, err := r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + rowsDeleted, err := w.Delete(ctx, actToken) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsDeleted != 1 { + return errors.New(ctx, errors.UnexpectedRowsAffected, op, fmt.Sprintf("expected to delete one activation token, deleted %d", rowsDeleted)) + } + return nil + }, + ) + if err != nil { + return errors.Wrap(ctx, err, op) + } + + return nil +} + func (r *WorkerAuthRepositoryStorage) removeCertificateAuthority(ctx context.Context, cert *types.RootCertificates) error { const op = "server.(WorkerAuthRepositoryStorage).removeCertificateAuthority" diff --git a/internal/server/repository_workerauth_test.go b/internal/server/repository_workerauth_test.go index 63736cc1d..8df8c3c81 100644 --- a/internal/server/repository_workerauth_test.go +++ b/internal/server/repository_workerauth_test.go @@ -2,13 +2,18 @@ package server import ( "context" + "crypto/hmac" "crypto/rand" + "crypto/sha256" + "strings" "testing" + "time" "github.com/fatih/structs" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/server/store" "github.com/hashicorp/boundary/internal/types/scope" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/nodeenrollment" @@ -17,9 +22,11 @@ import ( "github.com/hashicorp/nodeenrollment/storage/file" "github.com/hashicorp/nodeenrollment/types" "github.com/mitchellh/mapstructure" + "github.com/mr-tron/base58" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/curve25519" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" ) @@ -135,18 +142,14 @@ func TestStoreCertAuthorityVersioning(t *testing.T) { func TestStoreWorkerAuth(t *testing.T) { require, assert := require.New(t), assert.New(t) ctx := context.Background() - wrapper := db.TestWrapper(t) + rootWrapper := db.TestWrapper(t) conn, _ := db.TestSetup(t, "postgres") - kmsCache := kms.TestKms(t, conn, wrapper) + kmsCache := kms.TestKms(t, conn, rootWrapper) // Ensures the global scope contains a valid root key - err := kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)) - require.NoError(err) - wrapper, err = kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase) - require.NoError(err) - require.NotNil(wrapper) + require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) - worker := TestPkiWorker(t, conn, wrapper) + worker := TestPkiWorker(t, conn, rootWrapper) rw := db.New(conn) rootStorage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache) @@ -216,6 +219,49 @@ func TestStoreWorkerAuth(t *testing.T) { require.Error(err) } +func TestStoreServerLedActivationToken(t *testing.T) { + require, assert := require.New(t), assert.New(t) + ctx := context.Background() + rootWrapper := db.TestWrapper(t) + conn, _ := db.TestSetup(t, "postgres") + kmsCache := kms.TestKms(t, conn, rootWrapper) + + // Ensure the global scope contains a valid root key + require.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) + + rw := db.New(conn) + rootStorage, err := NewRepositoryStorage(ctx, rw, rw, kmsCache) + require.NoError(err) + + _, err = rotation.RotateRootCertificates(ctx, rootStorage) + require.NoError(err) + + repo, err := NewRepository(rw, rw, kmsCache) + require.NoError(err) + worker, err := repo.CreateWorker(ctx, &Worker{Worker: &store.Worker{ScopeId: scope.Global.String()}}, WithCreateControllerLedActivationToken(true)) + require.NoError(err) + require.NotEmpty(worker.ControllerGeneratedActivationToken) + + // We should now look for an activation token value in storage using the + // lookup function and validate that it's populated + tokenNonce := new(types.ServerLedActivationTokenNonce) + marshaledNonce, err := base58.FastBase58Decoding(strings.TrimPrefix(worker.ControllerGeneratedActivationToken, nodeenrollment.ServerLedActivationTokenPrefix)) + require.NoError(err) + require.NoError(proto.Unmarshal(marshaledNonce, tokenNonce)) + hm := hmac.New(sha256.New, tokenNonce.HmacKeyBytes) + idBytes := hm.Sum(tokenNonce.Nonce) + actToken := &types.ServerLedActivationToken{ + Id: base58.FastBase58Encoding(idBytes), + } + require.NoError(rootStorage.Load(ctx, actToken)) + require.NotNil(actToken.CreationTime) + assert.False(actToken.CreationTime.AsTime().IsZero()) + assert.Less(time.Until(actToken.CreationTime.AsTime()), time.Duration(0)) + + require.NoError(rootStorage.Remove(ctx, actToken)) + require.Error(rootStorage.Load(ctx, actToken)) +} + func TestUnsupportedMessages(t *testing.T) { require := require.New(t) ctx := context.Background() @@ -253,8 +299,7 @@ func TestStoreNodeInformationTx(t *testing.T) { kmsCache := kms.TestKms(t, conn, testWrapper) // Ensures the global scope contains a valid root key - err = kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader)) - require.NoError(t, err) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) databaseWrapper, err := kmsCache.GetWrapper(context.Background(), scope.Global.String(), kms.KeyPurposeDatabase) require.NoError(t, err) diff --git a/internal/server/store/gorm.go b/internal/server/store/gorm.go index 2da1ceae9..50b72d439 100644 --- a/internal/server/store/gorm.go +++ b/internal/server/store/gorm.go @@ -1,11 +1,11 @@ package store // TableName overrides the table name used by Controller to `server_controller` -func (Controller) TableName() string { +func (*Controller) TableName() string { return "server_controller" } // TableName overrides the table name used by WorkerTag to `worker_tag` -func (WorkerTag) TableName() string { +func (*WorkerTag) TableName() string { return "server_worker_tag" } diff --git a/internal/server/store/worker_auth.pb.go b/internal/server/store/worker_auth.pb.go index deb6485c6..f85c31886 100644 --- a/internal/server/store/worker_auth.pb.go +++ b/internal/server/store/worker_auth.pb.go @@ -203,6 +203,99 @@ func (x *WorkerCertBundle) GetCertBundle() []byte { return nil } +// WorkerAuthServerLedActivationToken contains all fields related to a +// WorkerAuthServerLedActivationToken resource +type WorkerAuthServerLedActivationToken struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The worker_id of the worker that this activates + // @inject_tag: `gorm:"primary_key"` + WorkerId string `protobuf:"bytes,10,opt,name=worker_id,json=workerId,proto3" json:"worker_id,omitempty" gorm:"primary_key"` + // The token identifier, which is used for lookup + // @inject_tag: `gorm:"not_null"` + TokenId string `protobuf:"bytes,15,opt,name=token_id,json=tokenId,proto3" json:"token_id,omitempty" gorm:"not_null"` + // The creation time, encrypted to prevent tampering, as the time plus + // existence of the record allows authorization + // @inject_tag: `gorm:"not_null" wrapping:"ct,creation_time_data"` + CreationTimeEncrypted []byte `protobuf:"bytes,20,opt,name=creation_time_encrypted,json=creationTimeEncrypted,proto3" json:"creation_time_encrypted,omitempty" gorm:"not_null" wrapping:"ct,creation_time_data"` + // The plaintext bytes of the creation time, which are never stored. This is a + // marshaled timestamppb.Timestamp. + // @inject_tag: `gorm:"-" wrapping:"pt,creation_time_data"` + CreationTime []byte `protobuf:"bytes,21,opt,name=creation_time,json=creationTime,proto3" json:"creation_time,omitempty" gorm:"-" wrapping:"pt,creation_time_data"` + // The key ID of the encrypting key + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,40,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` +} + +func (x *WorkerAuthServerLedActivationToken) Reset() { + *x = WorkerAuthServerLedActivationToken{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *WorkerAuthServerLedActivationToken) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*WorkerAuthServerLedActivationToken) ProtoMessage() {} + +func (x *WorkerAuthServerLedActivationToken) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use WorkerAuthServerLedActivationToken.ProtoReflect.Descriptor instead. +func (*WorkerAuthServerLedActivationToken) Descriptor() ([]byte, []int) { + return file_controller_storage_servers_store_v1_worker_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *WorkerAuthServerLedActivationToken) GetWorkerId() string { + if x != nil { + return x.WorkerId + } + return "" +} + +func (x *WorkerAuthServerLedActivationToken) GetTokenId() string { + if x != nil { + return x.TokenId + } + return "" +} + +func (x *WorkerAuthServerLedActivationToken) GetCreationTimeEncrypted() []byte { + if x != nil { + return x.CreationTimeEncrypted + } + return nil +} + +func (x *WorkerAuthServerLedActivationToken) GetCreationTime() []byte { + if x != nil { + return x.CreationTime + } + return nil +} + +func (x *WorkerAuthServerLedActivationToken) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + var File_controller_storage_servers_store_v1_worker_auth_proto protoreflect.FileDescriptor var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{ @@ -242,11 +335,24 @@ var file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc = []byte{ 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, 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, + 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 ( @@ -261,10 +367,11 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_rawDescGZIP() [] return file_controller_storage_servers_store_v1_worker_auth_proto_rawDescData } -var file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 3) var file_controller_storage_servers_store_v1_worker_auth_proto_goTypes = []interface{}{ - (*WorkerAuth)(nil), // 0: controller.storage.servers.store.v1.WorkerAuth - (*WorkerCertBundle)(nil), // 1: controller.storage.servers.store.v1.WorkerCertBundle + (*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 } var file_controller_storage_servers_store_v1_worker_auth_proto_depIdxs = []int32{ 0, // [0:0] is the sub-list for method output_type @@ -304,6 +411,18 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_init() { return nil } } + file_controller_storage_servers_store_v1_worker_auth_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkerAuthServerLedActivationToken); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -311,7 +430,7 @@ func file_controller_storage_servers_store_v1_worker_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_servers_store_v1_worker_auth_proto_rawDesc, NumEnums: 0, - NumMessages: 2, + NumMessages: 3, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/server/testing.go b/internal/server/testing.go index 87c7ca2c1..685e09625 100644 --- a/internal/server/testing.go +++ b/internal/server/testing.go @@ -99,7 +99,7 @@ func TestKmsWorker(t *testing.T, conn *db.DB, wrapper wrapping.Wrapper, opt ...O serversRepo, err := NewRepository(rw, rw, kms) require.NoError(t, err) ctx := context.Background() - opts := getOpts(opt...) + opts := GetOpts(opt...) namePart, err := newWorkerId(ctx) require.NoError(t, err) @@ -150,7 +150,7 @@ func TestPkiWorker(t *testing.T, conn *db.DB, wrapper wrapping.Wrapper, opt ...O serversRepo, err := NewRepository(rw, rw, kmsCache) require.NoError(t, err) ctx := context.Background() - opts := getOpts(opt...) + opts := GetOpts(opt...) require.NoError(t, err) wrk := NewWorker(scope.Global.String(), diff --git a/internal/server/testing_test.go b/internal/server/testing_test.go index 518a25e9d..184c0209b 100644 --- a/internal/server/testing_test.go +++ b/internal/server/testing_test.go @@ -2,10 +2,12 @@ package server import ( "context" + "crypto/rand" "strings" "testing" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/types/scope" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -38,6 +40,8 @@ func TestTestKmsWorker(t *testing.T) { func TestTestPkiWorker(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + require.NoError(t, kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) const ( name = "test name" description = "test description" diff --git a/internal/server/worker.go b/internal/server/worker.go index fbd0de465..99b1d6590 100644 --- a/internal/server/worker.go +++ b/internal/server/worker.go @@ -67,13 +67,16 @@ type Worker struct { // other based on the context in which the worker is passed. As such // inputTags should only be read when performing mutations on the database. inputTags []*Tag `gorm:"-"` + + // This is used to pass the token back to the calling function + ControllerGeneratedActivationToken string `gorm:"-"` } // NewWorker returns a new Worker. Valid options are WithName, WithDescription // WithAddress, and WithWorkerTags. All other options are ignored. This does // not set any of the worker reported values. func NewWorker(scopeId string, opt ...Option) *Worker { - opts := getOpts(opt...) + opts := GetOpts(opt...) return &Worker{ Worker: &store.Worker{ ScopeId: scopeId, diff --git a/internal/server/worker_auth.go b/internal/server/worker_auth.go index 720e2f66c..7733af1a0 100644 --- a/internal/server/worker_auth.go +++ b/internal/server/worker_auth.go @@ -24,7 +24,7 @@ type WorkerKeys struct { func newWorkerAuth(ctx context.Context, workerKeyIdentifier, workerId string, opt ...Option) (*WorkerAuth, error) { const op = "server.newWorkerAuth" - opts := getOpts(opt...) + opts := GetOpts(opt...) if workerKeyIdentifier == "" { return nil, errors.New(ctx, errors.InvalidParameter, op, "no workerKeyIdentifier") diff --git a/internal/server/worker_auth_server_led_activation_token.go b/internal/server/worker_auth_server_led_activation_token.go new file mode 100644 index 000000000..0f5d20607 --- /dev/null +++ b/internal/server/worker_auth_server_led_activation_token.go @@ -0,0 +1,113 @@ +package server + +import ( + "context" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/server/store" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping" + "google.golang.org/protobuf/proto" +) + +// WorkerAuthServerLedActivationToken contains an activation token for a worker +type WorkerAuthServerLedActivationToken struct { + *store.WorkerAuthServerLedActivationToken + tableName string `gorm:"-"` +} + +// newWorkerAuthServerLedActivation creates a new token from the given values +func newWorkerAuthServerLedActivationToken(ctx context.Context, workerId, tokenId string, creationTime []byte, _ ...Option) (*WorkerAuthServerLedActivationToken, error) { + const op = "server.newWorkerAuthServerLedActivationToken" + + switch { + case workerId == "": + return nil, errors.New(ctx, errors.InvalidParameter, op, "no worker id") + case tokenId == "": + return nil, errors.New(ctx, errors.InvalidParameter, op, "no token id") + case len(creationTime) == 0: + return nil, errors.New(ctx, errors.InvalidParameter, op, "empty creation time") + } + + l := &WorkerAuthServerLedActivationToken{ + WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{ + WorkerId: workerId, + TokenId: tokenId, + CreationTime: creationTime, + }, + } + + return l, nil +} + +func allocWorkerAuthServerLedActivationToken() *WorkerAuthServerLedActivationToken { + return &WorkerAuthServerLedActivationToken{ + WorkerAuthServerLedActivationToken: &store.WorkerAuthServerLedActivationToken{}, + } +} + +func (w *WorkerAuthServerLedActivationToken) clone() *WorkerAuthServerLedActivationToken { + cp := proto.Clone(w.WorkerAuthServerLedActivationToken) + return &WorkerAuthServerLedActivationToken{ + WorkerAuthServerLedActivationToken: cp.(*store.WorkerAuthServerLedActivationToken), + } +} + +// ValidateNewWorkerAuthServerLedActivationToken is called before storing a WorkerAuthActivationToken in the db +func (w *WorkerAuthServerLedActivationToken) ValidateNewWorkerAuthServerLedActivationToken(ctx context.Context) error { + const op = "server.(WorkerAuthServerLedActivationToken).ValidateNewWorkerAuthServerLedActivationToken" + if w.WorkerId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing worker id") + } + if w.TokenId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing token id") + } + if len(w.CreationTimeEncrypted) == 0 { + return errors.New(ctx, errors.InvalidParameter, op, "missing encrypted creation time") + } + + return nil +} + +// TableName returns the table name. +func (w *WorkerAuthServerLedActivationToken) TableName() string { + if w.tableName != "" { + return w.tableName + } + return "worker_auth_server_led_activation_token" +} + +// SetTableName sets the table name. +func (w *WorkerAuthServerLedActivationToken) SetTableName(n string) { + w.tableName = n +} + +// encrypt the activation token before storing +func (w *WorkerAuthServerLedActivationToken) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "server.(WorkerAuthServerLedActivationToken).encrypt" + if cipher == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + if err := structwrapping.WrapStruct(ctx, cipher, w.WorkerAuthServerLedActivationToken); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt)) + } + keyId, err := cipher.KeyId(ctx) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("failed to read cipher key id")) + } + w.KeyId = keyId + + return nil +} + +// decrypt the auth method after reading it from the db +func (w *WorkerAuthServerLedActivationToken) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "server.(WorkerAuthServerLedActivationToken).decrypt" + if cipher == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + if err := structwrapping.UnwrapStruct(ctx, cipher, w.WorkerAuthServerLedActivationToken); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt)) + } + return nil +} diff --git a/internal/server/worker_auth_server_led_activation_token_test.go b/internal/server/worker_auth_server_led_activation_token_test.go new file mode 100644 index 000000000..e7d28eda0 --- /dev/null +++ b/internal/server/worker_auth_server_led_activation_token_test.go @@ -0,0 +1,122 @@ +package server + +import ( + "context" + "crypto/rand" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/go-dbw" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWorkerAuthActivationTokenConstraints(t *testing.T) { + t.Parallel() + tlRequire, tlAssert := require.New(t), assert.New(t) + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + wrap := db.TestWrapper(t) + + rw := db.New(conn) + kmsCache := kms.TestKms(t, conn, wrap) + tlRequire.NoError(kmsCache.CreateKeys(context.Background(), scope.Global.String(), kms.WithRandomReader(rand.Reader))) + + repo, err := NewRepository(rw, rw, kmsCache) + tlRequire.NoError(err) + + // First create a worker without an activation token so we can verify it doesn't show up in the table + origWorker := NewWorker(scope.Global.String()) + origWorker, err = repo.CreateWorker(ctx, origWorker) + tlRequire.NoError(err) + tlAssert.Empty(origWorker.ControllerGeneratedActivationToken) + activationToken := allocWorkerAuthServerLedActivationToken() + err = rw.LookupWhere(ctx, activationToken, "worker_id = ?", []any{origWorker.PublicId}) + tlRequire.Error(err) + tlAssert.ErrorIs(err, dbw.ErrRecordNotFound) + + // Now create a worker with an activation token and verify we can find it + worker := NewWorker(scope.Global.String()) + worker, err = repo.CreateWorker(ctx, worker, WithCreateControllerLedActivationToken(true)) + tlRequire.NoError(err) + tlAssert.NotEmpty(worker.ControllerGeneratedActivationToken) + activationToken = allocWorkerAuthServerLedActivationToken() + err = rw.LookupWhere(ctx, activationToken, "worker_id = ?", []any{worker.PublicId}) + tlRequire.NoError(err) + tlAssert.Empty(activationToken.CreationTime) + tlAssert.NotEmpty(activationToken.KeyId) + tlAssert.NotEmpty(activationToken.TokenId) + tlAssert.NotEmpty(activationToken.CreationTimeEncrypted) + + // Run some test cases. All are expected to result in error. + cases := []struct { + name string + createModifyFn func(*testing.T, *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken + updateModifyFn func(*testing.T, *WorkerAuthServerLedActivationToken) (string, []any) // If set, will update instead of create + wantErrContains string + }{ + { + name: "fully-duplicate", + createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken { + return in + }, + wantErrContains: "worker_auth_server_led_activation_token_pkey", + }, + { + name: "same-token-id", + createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken { + in.WorkerId = origWorker.PublicId + return in + }, + wantErrContains: "worker_auth_server_led_activation_token_token_id_uq", + }, + { + name: "same-worker-id", + createModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) *WorkerAuthServerLedActivationToken { + in.TokenId = in.TokenId[0:5] + return in + }, + wantErrContains: "worker_auth_server_led_activation_token_pkey", + }, + { + name: "modify-worker-id", + updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) { + return "update worker_auth_server_led_activation_token set worker_id = ? where token_id = ?", []any{origWorker.PublicId, in.TokenId} + }, + wantErrContains: "immutable column: worker_auth_server_led_activation_token.worker_id: integrity violation", + }, + { + name: "modify-token-id", + updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) { + return "update worker_auth_server_led_activation_token set token_id = ? where worker_id = ?", []any{in.TokenId[0:5], in.WorkerId} + }, + wantErrContains: "immutable column: worker_auth_server_led_activation_token.token_id: integrity violation", + }, + { + name: "modify-create-time", + updateModifyFn: func(t *testing.T, in *WorkerAuthServerLedActivationToken) (string, []any) { + return "update worker_auth_server_led_activation_token set creation_time_encrypted = ? where worker_id = ?", []any{in.CreationTimeEncrypted[0:5], in.WorkerId} + }, + wantErrContains: "immutable column: worker_auth_server_led_activation_token.creation_time_encrypted: integrity violation", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + require, assert := require.New(t), assert.New(t) + testActivationToken := activationToken.clone() + var err error + if tc.updateModifyFn != nil { + queryStr, args := tc.updateModifyFn(t, testActivationToken) + var rowsUpdated int + rowsUpdated, err = rw.Exec(ctx, queryStr, args) + assert.Empty(rowsUpdated) + } else { + err = rw.Create(ctx, tc.createModifyFn(t, testActivationToken)) + } + require.Error(err) + assert.Contains(err.Error(), tc.wantErrContains) + }) + } +} diff --git a/internal/types/action/action.go b/internal/types/action/action.go index ed8bacc3a..b9632d972 100644 --- a/internal/types/action/action.go +++ b/internal/types/action/action.go @@ -57,6 +57,7 @@ const ( AddWorkerTags Type = 46 SetWorkerTags Type = 47 RemoveWorkerTags Type = 48 + CreateControllerLed Type = 49 // When adding new actions, be sure to update: // @@ -112,6 +113,7 @@ var Map = map[string]Type{ AddWorkerTags.String(): AddWorkerTags, SetWorkerTags.String(): SetWorkerTags, RemoveWorkerTags.String(): RemoveWorkerTags, + CreateControllerLed.String(): CreateControllerLed, } func (a Type) String() string { @@ -165,6 +167,7 @@ func (a Type) String() string { "add-worker-tags", "set-worker-tags", "remove-worker-tags", + "create:controller-led", }[a] } diff --git a/internal/types/action/action_test.go b/internal/types/action/action_test.go index e644d3976..82dbbb70c 100644 --- a/internal/types/action/action_test.go +++ b/internal/types/action/action_test.go @@ -99,6 +99,22 @@ func TestAction(t *testing.T) { action: CreateWorkerLed, want: "create:worker-led", }, + { + action: AddWorkerTags, + want: "add-worker-tags", + }, + { + action: SetWorkerTags, + want: "set-worker-tags", + }, + { + action: RemoveWorkerTags, + want: "remove-worker-tags", + }, + { + action: CreateControllerLed, + want: "create:controller-led", + }, } for _, tt := range tests { t.Run(tt.want, func(t *testing.T) { diff --git a/internal/website/permstable/permstable.go b/internal/website/permstable/permstable.go index 72c628010..38f42c1a4 100644 --- a/internal/website/permstable/permstable.go +++ b/internal/website/permstable/permstable.go @@ -846,6 +846,14 @@ var worker = &Resource{ }, Actions: append( lActions("a worker"), + &Action{ + Name: "create:controller-led", + Description: "Create a worker using the controller-led workflow", + Examples: []string{ + "type=;actions=create", + "type=;actions=create:controller-led", + }, + }, &Action{ Name: "create:worker-led", Description: "Create a worker using the worker-led workflow", diff --git a/sdk/pbs/controller/api/resources/workers/worker.pb.go b/sdk/pbs/controller/api/resources/workers/worker.pb.go index 1e377d4a3..0fca6402a 100644 --- a/sdk/pbs/controller/api/resources/workers/worker.pb.go +++ b/sdk/pbs/controller/api/resources/workers/worker.pb.go @@ -66,6 +66,9 @@ type Worker struct { // token is a base58 encoded types.FetchNodeCredentialsRequest from // https://github.com/hashicorp/nodeenrollment WorkerGeneratedAuthToken *wrapperspb.StringValue `protobuf:"bytes,150,opt,name=worker_generated_auth_token,proto3" json:"worker_generated_auth_token,omitempty" class:"public"` // @gotags: `class:"public"` + // Output only. An activation token that can be given to a worker to correlate + // it to the created resource. + ControllerGeneratedActivationToken *wrapperspb.StringValue `protobuf:"bytes,151,opt,name=controller_generated_activation_token,proto3" json:"controller_generated_activation_token,omitempty" class:"secret"` // @gotags: `class:"secret"` // Output only. The number of connections that this worker is currently handling. ActiveConnectionCount *wrapperspb.UInt32Value `protobuf:"bytes,160,opt,name=active_connection_count,proto3" json:"active_connection_count,omitempty" class:"public"` // @gotags: `class:"public"` // Output only. The type of the worker, denoted by how it authenticates: `pki` @@ -200,6 +203,13 @@ func (x *Worker) GetWorkerGeneratedAuthToken() *wrapperspb.StringValue { return nil } +func (x *Worker) GetControllerGeneratedActivationToken() *wrapperspb.StringValue { + if x != nil { + return x.ControllerGeneratedActivationToken + } + return nil +} + func (x *Worker) GetActiveConnectionCount() *wrapperspb.UInt32Value { if x != nil { return x.ActiveConnectionCount @@ -248,7 +258,7 @@ var file_controller_api_resources_workers_v1_worker_proto_rawDesc = []byte{ 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0xd2, 0x0a, 0x0a, 0x06, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x0e, 0x0a, + 0x74, 0x6f, 0x22, 0xc7, 0x0b, 0x0a, 0x06, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, @@ -301,45 +311,52 @@ var file_controller_api_resources_workers_v1_worker_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x1b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x12, 0x57, 0x0a, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0xa0, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0xaa, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x55, - 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0xb4, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, - 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x77, 0x6f, 0x72, - 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x2e, 0x41, - 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x61, 0x70, 0x69, - 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xac, 0x02, 0x20, 0x03, - 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x5c, 0x0a, 0x12, 0x43, 0x61, 0x6e, 0x6f, 0x6e, 0x69, - 0x63, 0x61, 0x6c, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x12, 0x73, 0x0a, 0x25, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x97, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x25, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x57, 0x0a, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, + 0x18, 0xa0, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x49, 0x6e, 0x74, 0x33, 0x32, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x17, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x13, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0xaa, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x55, 0x0a, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, + 0xb4, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2e, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x2e, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x08, 0x61, 0x70, 0x69, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x12, 0x2f, 0x0a, 0x12, 0x61, 0x75, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0xac, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x5c, 0x0a, 0x12, 0x43, + 0x61, 0x6e, 0x6f, 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x59, 0x0a, 0x0f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x61, - 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, - 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x50, 0x5a, 0x4e, 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, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, - 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, - 0x73, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x56, 0x0a, 0x0c, 0x41, 0x70, 0x69, 0x54, 0x61, 0x67, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x50, 0x5a, 0x4e, + 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, 0x73, 0x64, + 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, + 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x77, + 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x3b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x73, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -376,16 +393,17 @@ var file_controller_api_resources_workers_v1_worker_proto_depIdxs = []int32{ 2, // 6: controller.api.resources.workers.v1.Worker.config_tags:type_name -> controller.api.resources.workers.v1.Worker.ConfigTagsEntry 6, // 7: controller.api.resources.workers.v1.Worker.last_status_time:type_name -> google.protobuf.Timestamp 5, // 8: controller.api.resources.workers.v1.Worker.worker_generated_auth_token:type_name -> google.protobuf.StringValue - 7, // 9: controller.api.resources.workers.v1.Worker.active_connection_count:type_name -> google.protobuf.UInt32Value - 3, // 10: controller.api.resources.workers.v1.Worker.api_tags:type_name -> controller.api.resources.workers.v1.Worker.ApiTagsEntry - 8, // 11: controller.api.resources.workers.v1.Worker.CanonicalTagsEntry.value:type_name -> google.protobuf.ListValue - 8, // 12: controller.api.resources.workers.v1.Worker.ConfigTagsEntry.value:type_name -> google.protobuf.ListValue - 8, // 13: controller.api.resources.workers.v1.Worker.ApiTagsEntry.value:type_name -> google.protobuf.ListValue - 14, // [14:14] is the sub-list for method output_type - 14, // [14:14] is the sub-list for method input_type - 14, // [14:14] is the sub-list for extension type_name - 14, // [14:14] is the sub-list for extension extendee - 0, // [0:14] is the sub-list for field type_name + 5, // 9: controller.api.resources.workers.v1.Worker.controller_generated_activation_token:type_name -> google.protobuf.StringValue + 7, // 10: controller.api.resources.workers.v1.Worker.active_connection_count:type_name -> google.protobuf.UInt32Value + 3, // 11: controller.api.resources.workers.v1.Worker.api_tags:type_name -> controller.api.resources.workers.v1.Worker.ApiTagsEntry + 8, // 12: controller.api.resources.workers.v1.Worker.CanonicalTagsEntry.value:type_name -> google.protobuf.ListValue + 8, // 13: controller.api.resources.workers.v1.Worker.ConfigTagsEntry.value:type_name -> google.protobuf.ListValue + 8, // 14: controller.api.resources.workers.v1.Worker.ApiTagsEntry.value:type_name -> google.protobuf.ListValue + 15, // [15:15] is the sub-list for method output_type + 15, // [15:15] is the sub-list for method input_type + 15, // [15:15] is the sub-list for extension type_name + 15, // [15:15] is the sub-list for extension extendee + 0, // [0:15] is the sub-list for field type_name } func init() { file_controller_api_resources_workers_v1_worker_proto_init() } diff --git a/website/content/docs/concepts/security/permissions/resource-table.mdx b/website/content/docs/concepts/security/permissions/resource-table.mdx index 167621c1f..5826df8aa 100644 --- a/website/content/docs/concepts/security/permissions/resource-table.mdx +++ b/website/content/docs/concepts/security/permissions/resource-table.mdx @@ -1472,6 +1472,17 @@ documentation](https://www.boundaryproject.io/api-docs) for guidance. type=<type>;actions=list +
  • + create:controller-led: Create a worker using the controller-led workflow +
  • +
  • create:worker-led: Create a worker using the worker-led workflow
  • diff --git a/website/content/docs/configuration/worker/pki-worker.mdx b/website/content/docs/configuration/worker/pki-worker.mdx index 09398288f..cc1a13c0d 100644 --- a/website/content/docs/configuration/worker/pki-worker.mdx +++ b/website/content/docs/configuration/worker/pki-worker.mdx @@ -22,6 +22,46 @@ worker { } ``` +## Authorization Methods +There are two mechanisms that can be used to register a worker to the cluster. + +### Controller-Led Authorization Flow +In this flow, the operator fetches an activation token from the controller's +`workers:create:controller-led` action (on the CLI, this is via `boundary +workers create controller-led`). That activation token is given to the worker +via the `controller_generated_activation_token` parameter. This can be done +either directly or via an env var or file by using `env://` or `file://` syntax: + +```hcl +worker { + worker { + auth_storage_path="/boundary/demo-worker-1" + initial_upstreams = ["10.0.0.1"] + controller_generated_activation_token = "neslat_........." + # controller_generated_activation_token = "env://ACT_TOKEN" + # controller_generated_activation_token = "file:///tmp/worker_act_token" +} +``` + +Once the worker starts, it will read this token and use it to authorize to the +cluster. Note that this token is one-time-use; it is safe to keep it here even +after the worker has successfully authorized and authenticated, as it will be +unusable at that point. + +Note: If this value is not present at worker startup time and the worker is not +authorized, it will print and write out suitable information for the worker-led +flow, described below. So long as the worker-led flow has not been used to +authorize the worker, if the controller-generated activation token is provided +and the worker restarted, it will make use of it. + +### Worker-Led Authorization Flow +In this flow, the worker prints out an authorization request token to two +places: the startup information printed to stdout, and a file called +`auth_request_token` in the base of the configured `auth_storage_path`. This +token can be submitted to a controller at the `workers:create:worker-led` path; +on the CLI this would be via `boundary workers create worker-led +-worker-generated-auth-token`. No values are needed in the configuration file. + ## KMS Configuration PKI Workers credentials can be encrypted by including an optional KMS stanza with the purpose `worker-auth-storage`.