From 01fb949d0b529646363e4f2c9813d4e98db5f0ce Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Fri, 2 Sep 2022 14:32:41 -0400 Subject: [PATCH] Add controller-led worker auth flow (#2413) This adds an action to the API that allows pulling out an activation token that can be given to a worker in its config (directly or via env or file). The worker can pass this along with its normal request in place of its normal nonce and the server will automatically accept it. Co-authored-by: Irena Rindos --- CHANGELOG.md | 52 +- api/workers/worker.gen.go | 79 +- globals/fields.go | 1 + go.mod | 2 +- go.sum | 4 +- internal/api/genapi/input.go | 183 +++-- internal/api/genapi/templates.go | 9 +- internal/cmd/commands.go | 6 + internal/cmd/commands/dev/dev.go | 126 +++- internal/cmd/commands/dev/flags_test.go | 2 +- internal/cmd/commands/server/server.go | 4 + .../workerscmd/controller-led_funcs.go | 40 + .../workerscmd/controller-led_workers.gen.go | 247 ++++++ internal/cmd/commands/workerscmd/funcs.go | 3 + internal/cmd/config/config.go | 10 + internal/cmd/config/config_test.go | 18 + internal/cmd/gencli/input.go | 14 + .../cluster/handlers/worker_service_test.go | 13 +- .../handlers/scopes/scope_service_test.go | 1 + .../handlers/targets/target_service_test.go | 5 + .../handlers/workers/worker_service.go | 105 ++- .../handlers/workers/worker_service_test.go | 355 ++++++++- .../daemon/worker/controller_connection.go | 35 +- internal/daemon/worker/worker.go | 13 + .../48/01_worker_auth_activation_token.up.sql | 30 + internal/gen/controller.swagger.json | 43 ++ .../api/services/worker_service.pb.go | 711 +++++++++++------- .../api/services/worker_service.pb.gw.go | 94 +++ .../api/services/worker_service_grpc.pb.go | 70 +- internal/perms/acl_test.go | 2 +- .../api/resources/workers/v1/worker.proto | 4 + .../api/services/v1/worker_service.proto | 37 +- .../servers/store/v1/worker_auth.proto | 26 + internal/server/options.go | 52 +- internal/server/options_test.go | 40 +- internal/server/repository.go | 2 +- internal/server/repository_controller.go | 2 +- internal/server/repository_worker.go | 70 +- internal/server/repository_worker_test.go | 89 ++- internal/server/repository_workerauth.go | 110 ++- internal/server/repository_workerauth_test.go | 65 +- internal/server/store/gorm.go | 4 +- internal/server/store/worker_auth.pb.go | 137 +++- internal/server/testing.go | 4 +- internal/server/testing_test.go | 4 + internal/server/worker.go | 5 +- internal/server/worker_auth.go | 2 +- ...worker_auth_server_led_activation_token.go | 113 +++ ...r_auth_server_led_activation_token_test.go | 122 +++ internal/types/action/action.go | 3 + internal/types/action/action_test.go | 16 + internal/website/permstable/permstable.go | 8 + .../api/resources/workers/worker.pb.go | 110 +-- .../security/permissions/resource-table.mdx | 11 + .../docs/configuration/worker/pki-worker.mdx | 40 + 55 files changed, 2728 insertions(+), 625 deletions(-) create mode 100644 internal/cmd/commands/workerscmd/controller-led_funcs.go create mode 100644 internal/cmd/commands/workerscmd/controller-led_workers.gen.go create mode 100644 internal/db/schema/migrations/oss/postgres/48/01_worker_auth_activation_token.up.sql create mode 100644 internal/server/worker_auth_server_led_activation_token.go create mode 100644 internal/server/worker_auth_server_led_activation_token_test.go 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 +
  • +
      +
    • + type=<type>;actions=create +
    • +
    • + type=<type>;actions=create:controller-led +
    • +
  • 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`.