diff --git a/.circleci/config.yml b/.circleci/config.yml index e2d0dc8d0..28f60e6f6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -946,6 +946,7 @@ jobs: command: | make test-ci name: Run Acceptance Tests + no_output_timeout: 30m - run: command: | echo 'export SLACK_BUILD_STATUS="fail"' >> $BASH_ENV diff --git a/.circleci/config/jobs/build.yml b/.circleci/config/jobs/build.yml index 5ea574e12..2289ec8c1 100644 --- a/.circleci/config/jobs/build.yml +++ b/.circleci/config/jobs/build.yml @@ -5,6 +5,7 @@ steps: - checkout - run: name: "Run Acceptance Tests" + no_output_timeout: 15m command: | make test-ci - slack/status: diff --git a/Makefile b/Makefile index b483dcb05..19cf168ab 100644 --- a/Makefile +++ b/Makefile @@ -134,6 +134,8 @@ protobuild: @protoc-go-inject-tag -input=./internal/target/store/target.pb.go @protoc-go-inject-tag -input=./internal/auth/oidc/store/oidc.pb.go @protoc-go-inject-tag -input=./internal/scheduler/job/store/job.pb.go + @protoc-go-inject-tag -input=./internal/credential/store/credential.pb.go + @protoc-go-inject-tag -input=./internal/credential/vault/store/vault.pb.go @rm -R ${TMP_DIR} diff --git a/api/accounts/account.gen.go b/api/accounts/account.gen.go index bcad294d8..563f1f7a9 100644 --- a/api/accounts/account.gen.go +++ b/api/accounts/account.gen.go @@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, authMethodId string, opt ...Option) return target, nil } -func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*AccountReadResult, error) { - if accountId == "" { - return nil, fmt.Errorf("empty accountId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AccountReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*Ac opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("accounts/%s", accountId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("accounts/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, accountId string, opt ...Option) (*Ac return target, nil } -func (c *Client) Update(ctx context.Context, accountId string, version uint32, opt ...Option) (*AccountUpdateResult, error) { - if accountId == "" { - return nil, fmt.Errorf("empty accountId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*AccountUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, accountId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("accounts/%s", accountId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("accounts/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, accountId string, version uint32, o return target, nil } -func (c *Client) Delete(ctx context.Context, accountId string, opt ...Option) (*AccountDeleteResult, error) { - if accountId == "" { - return nil, fmt.Errorf("empty accountId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AccountDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, accountId string, opt ...Option) (* opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("accounts/%s", accountId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("accounts/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/authmethods/authmethods.gen.go b/api/authmethods/authmethods.gen.go index 839bc2c34..881187000 100644 --- a/api/authmethods/authmethods.gen.go +++ b/api/authmethods/authmethods.gen.go @@ -141,9 +141,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string return target, nil } -func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethodReadResult, error) { - if authMethodId == "" { - return nil, fmt.Errorf("empty authMethodId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AuthMethodReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -151,7 +151,7 @@ func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) ( opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-methods/%s", authMethodId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-methods/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -182,9 +182,9 @@ func (c *Client) Read(ctx context.Context, authMethodId string, opt ...Option) ( return target, nil } -func (c *Client) Update(ctx context.Context, authMethodId string, version uint32, opt ...Option) (*AuthMethodUpdateResult, error) { - if authMethodId == "" { - return nil, fmt.Errorf("empty authMethodId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*AuthMethodUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -196,7 +196,7 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, authMethodId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -214,7 +214,7 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32 opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("auth-methods/%s", authMethodId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("auth-methods/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -245,9 +245,9 @@ func (c *Client) Update(ctx context.Context, authMethodId string, version uint32 return target, nil } -func (c *Client) Delete(ctx context.Context, authMethodId string, opt ...Option) (*AuthMethodDeleteResult, error) { - if authMethodId == "" { - return nil, fmt.Errorf("empty authMethodId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AuthMethodDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -255,7 +255,7 @@ func (c *Client) Delete(ctx context.Context, authMethodId string, opt ...Option) opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-methods/%s", authMethodId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-methods/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/authtokens/authtokens.gen.go b/api/authtokens/authtokens.gen.go index 1e178bcbc..5eb28dfde 100644 --- a/api/authtokens/authtokens.gen.go +++ b/api/authtokens/authtokens.gen.go @@ -90,9 +90,9 @@ func (c *Client) ApiClient() *api.Client { return c.client } -func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (*AuthTokenReadResult, error) { - if authTokenId == "" { - return nil, fmt.Errorf("empty authTokenId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*AuthTokenReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -100,7 +100,7 @@ func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (* opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-tokens/%s", authTokenId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("auth-tokens/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -131,9 +131,9 @@ func (c *Client) Read(ctx context.Context, authTokenId string, opt ...Option) (* return target, nil } -func (c *Client) Delete(ctx context.Context, authTokenId string, opt ...Option) (*AuthTokenDeleteResult, error) { - if authTokenId == "" { - return nil, fmt.Errorf("empty authTokenId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*AuthTokenDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -141,7 +141,7 @@ func (c *Client) Delete(ctx context.Context, authTokenId string, opt ...Option) opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-tokens/%s", authTokenId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("auth-tokens/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/credentiallibraries/credential_library.gen.go b/api/credentiallibraries/credential_library.gen.go new file mode 100644 index 000000000..c9357303f --- /dev/null +++ b/api/credentiallibraries/credential_library.gen.go @@ -0,0 +1,322 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentiallibraries + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" +) + +type CredentialLibrary struct { + Id string `json:"id,omitempty"` + CredentialStoreId string `json:"credential_store_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"` + Type string `json:"type,omitempty"` + Attributes map[string]interface{} `json:"attributes,omitempty"` + AuthorizedActions []string `json:"authorized_actions,omitempty"` + + response *api.Response +} + +type CredentialLibraryReadResult struct { + Item *CredentialLibrary + response *api.Response +} + +func (n CredentialLibraryReadResult) GetItem() interface{} { + return n.Item +} + +func (n CredentialLibraryReadResult) GetResponse() *api.Response { + return n.response +} + +type ( + CredentialLibraryCreateResult = CredentialLibraryReadResult + CredentialLibraryUpdateResult = CredentialLibraryReadResult +) + +type CredentialLibraryDeleteResult struct { + response *api.Response +} + +// GetItem will always be nil for CredentialLibraryDeleteResult +func (n CredentialLibraryDeleteResult) GetItem() interface{} { + return nil +} + +func (n CredentialLibraryDeleteResult) GetResponse() *api.Response { + return n.response +} + +type CredentialLibraryListResult struct { + Items []*CredentialLibrary + response *api.Response +} + +func (n CredentialLibraryListResult) GetItems() interface{} { + return n.Items +} + +func (n CredentialLibraryListResult) GetResponse() *api.Response { + return n.response +} + +// Client is a client for this collection +type Client struct { + client *api.Client +} + +// Creates a new client for this collection. The submitted API client is cloned; +// modifications to it after generating this client will not have effect. If you +// need to make changes to the underlying API client, use ApiClient() to access +// it. +func NewClient(c *api.Client) *Client { + return &Client{client: c.Clone()} +} + +// ApiClient returns the underlying API client +func (c *Client) ApiClient() *api.Client { + return c.client +} + +func (c *Client) Create(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialLibraryCreateResult, error) { + if credentialStoreId == "" { + return nil, fmt.Errorf("empty credentialStoreId value passed into Create request") + } + + opts, apiOpts := getOpts(opt...) + + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts.postMap["credential_store_id"] = credentialStoreId + + req, err := c.client.NewRequest(ctx, "POST", "credential-libraries", opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Create 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 Create call: %w", err) + } + + target := new(CredentialLibraryCreateResult) + target.Item = new(CredentialLibrary) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Create 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) (*CredentialLibraryReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("credential-libraries/%s", id), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Read 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, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error performing client request during Read call: %w", err) + } + + target := new(CredentialLibraryReadResult) + target.Item = new(CredentialLibrary) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Read response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*CredentialLibraryUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("credential-libraries/%s", id), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Update 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 Update call: %w", err) + } + + target := new(CredentialLibraryUpdateResult) + target.Item = new(CredentialLibrary) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Update response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*CredentialLibraryDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("credential-libraries/%s", id), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Delete 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 Delete call: %w", err) + } + + apiErr, err := resp.Decode(nil) + if err != nil { + return nil, fmt.Errorf("error decoding Delete response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + + target := &CredentialLibraryDeleteResult{ + response: resp, + } + return target, nil +} + +func (c *Client) List(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialLibraryListResult, error) { + if credentialStoreId == "" { + return nil, fmt.Errorf("empty credentialStoreId value passed into List request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + opts.queryMap["credential_store_id"] = credentialStoreId + + req, err := c.client.NewRequest(ctx, "GET", "credential-libraries", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List 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 List call: %w", err) + } + + target := new(CredentialLibraryListResult) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} diff --git a/api/credentiallibraries/option.gen.go b/api/credentiallibraries/option.gen.go new file mode 100644 index 000000000..eb401ca59 --- /dev/null +++ b/api/credentiallibraries/option.gen.go @@ -0,0 +1,171 @@ +package credentiallibraries + +import ( + "strings" + + "github.com/hashicorp/boundary/api" +) + +// Option is a func that sets optional attributes for a call. This does not need +// to be used directly, but instead option arguments are built from the +// functions in this package. WithX options set a value to that given in the +// argument; DefaultX options indicate that the value should be set to its +// default. When an API call is made options are processed in ther order they +// appear in the function call, so for a given argument X, a succession of WithX +// or DefaultX calls will result in the last call taking effect. +type Option func(*options) + +type options struct { + postMap map[string]interface{} + queryMap map[string]string + withAutomaticVersioning bool + withSkipCurlOutput bool + withFilter string +} + +func getDefaultOptions() options { + return options{ + postMap: make(map[string]interface{}), + queryMap: make(map[string]string), + } +} + +func getOpts(opt ...Option) (options, []api.Option) { + opts := getDefaultOptions() + for _, o := range opt { + if o != nil { + o(&opts) + } + } + var apiOpts []api.Option + if opts.withSkipCurlOutput { + apiOpts = append(apiOpts, api.WithSkipCurlOutput(true)) + } + if opts.withFilter != "" { + opts.queryMap["filter"] = opts.withFilter + } + return opts, apiOpts +} + +// If set, and if the version is zero during an update, the API will perform a +// fetch to get the current version of the resource and populate it during the +// update call. This is convenient but opens up the possibility for subtle +// order-of-modification issues, so use carefully. +func WithAutomaticVersioning(enable bool) Option { + return func(o *options) { + o.withAutomaticVersioning = enable + } +} + +// WithSkipCurlOutput tells the API to not use the current call for cURL output. +// Useful for when we need to look up versions. +func WithSkipCurlOutput(skip bool) Option { + return func(o *options) { + o.withSkipCurlOutput = true + } +} + +// WithFilter tells the API to filter the items returned using the provided +// filter term. The filter should be in a format supported by +// hashicorp/go-bexpr. +func WithFilter(filter string) Option { + return func(o *options) { + o.withFilter = strings.TrimSpace(filter) + } +} + +func WithAttributes(inAttributes map[string]interface{}) Option { + return func(o *options) { + o.postMap["attributes"] = inAttributes + } +} + +func DefaultAttributes() Option { + return func(o *options) { + o.postMap["attributes"] = nil + } +} + +func WithDescription(inDescription string) Option { + return func(o *options) { + o.postMap["description"] = inDescription + } +} + +func DefaultDescription() Option { + return func(o *options) { + o.postMap["description"] = nil + } +} + +func WithVaultCredentialLibraryHttpMethod(inHttpMethod string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["http_method"] = inHttpMethod + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialLibraryHttpMethod() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["http_method"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialLibraryHttpRequestBody(inHttpRequestBody string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["http_request_body"] = inHttpRequestBody + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialLibraryHttpRequestBody() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["http_request_body"] = nil + o.postMap["attributes"] = val + } +} + +func WithName(inName string) Option { + return func(o *options) { + o.postMap["name"] = inName + } +} + +func DefaultName() Option { + return func(o *options) { + o.postMap["name"] = nil + } +} + +func WithVaultCredentialLibraryPath(inPath string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["path"] = inPath + o.postMap["attributes"] = val + } +} diff --git a/api/credentiallibraries/vault_credential_library_attributes.gen.go b/api/credentiallibraries/vault_credential_library_attributes.gen.go new file mode 100644 index 000000000..71b0c0162 --- /dev/null +++ b/api/credentiallibraries/vault_credential_library_attributes.gen.go @@ -0,0 +1,8 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentiallibraries + +type VaultCredentialLibraryAttributes struct { + Path string `json:"path,omitempty"` + HttpMethod string `json:"http_method,omitempty"` + HttpRequestBody string `json:"http_request_body,omitempty"` +} diff --git a/api/credentialstores/credential_store.gen.go b/api/credentialstores/credential_store.gen.go new file mode 100644 index 000000000..a59c0e813 --- /dev/null +++ b/api/credentialstores/credential_store.gen.go @@ -0,0 +1,328 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentialstores + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" +) + +type CredentialStore 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"` + Type string `json:"type,omitempty"` + Attributes map[string]interface{} `json:"attributes,omitempty"` + AuthorizedActions []string `json:"authorized_actions,omitempty"` + AuthorizedCollectionActions map[string][]string `json:"authorized_collection_actions,omitempty"` + + response *api.Response +} + +type CredentialStoreReadResult struct { + Item *CredentialStore + response *api.Response +} + +func (n CredentialStoreReadResult) GetItem() interface{} { + return n.Item +} + +func (n CredentialStoreReadResult) GetResponse() *api.Response { + return n.response +} + +type ( + CredentialStoreCreateResult = CredentialStoreReadResult + CredentialStoreUpdateResult = CredentialStoreReadResult +) + +type CredentialStoreDeleteResult struct { + response *api.Response +} + +// GetItem will always be nil for CredentialStoreDeleteResult +func (n CredentialStoreDeleteResult) GetItem() interface{} { + return nil +} + +func (n CredentialStoreDeleteResult) GetResponse() *api.Response { + return n.response +} + +type CredentialStoreListResult struct { + Items []*CredentialStore + response *api.Response +} + +func (n CredentialStoreListResult) GetItems() interface{} { + return n.Items +} + +func (n CredentialStoreListResult) GetResponse() *api.Response { + return n.response +} + +// Client is a client for this collection +type Client struct { + client *api.Client +} + +// Creates a new client for this collection. The submitted API client is cloned; +// modifications to it after generating this client will not have effect. If you +// need to make changes to the underlying API client, use ApiClient() to access +// it. +func NewClient(c *api.Client) *Client { + return &Client{client: c.Clone()} +} + +// ApiClient returns the underlying API client +func (c *Client) ApiClient() *api.Client { + return c.client +} + +func (c *Client) Create(ctx context.Context, resourceType string, scopeId string, opt ...Option) (*CredentialStoreCreateResult, error) { + if scopeId == "" { + return nil, fmt.Errorf("empty scopeId value passed into Create request") + } + + opts, apiOpts := getOpts(opt...) + + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + if resourceType == "" { + return nil, fmt.Errorf("empty resourceType value passed into Create request") + } else { + opts.postMap["type"] = resourceType + } + + opts.postMap["scope_id"] = scopeId + + req, err := c.client.NewRequest(ctx, "POST", "credential-stores", opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Create 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 Create call: %w", err) + } + + target := new(CredentialStoreCreateResult) + target.Item = new(CredentialStore) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Create 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) (*CredentialStoreReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("credential-stores/%s", id), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Read 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, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error performing client request during Read call: %w", err) + } + + target := new(CredentialStoreReadResult) + target.Item = new(CredentialStore) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Read response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*CredentialStoreUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("credential-stores/%s", id), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Update 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 Update call: %w", err) + } + + target := new(CredentialStoreUpdateResult) + target.Item = new(CredentialStore) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding Update response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*CredentialStoreDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("credential-stores/%s", id), nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating Delete 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 Delete call: %w", err) + } + + apiErr, err := resp.Decode(nil) + if err != nil { + return nil, fmt.Errorf("error decoding Delete response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + + target := &CredentialStoreDeleteResult{ + response: resp, + } + return target, nil +} + +func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*CredentialStoreListResult, error) { + if scopeId == "" { + return nil, fmt.Errorf("empty scopeId value passed into List request") + } + if c.client == nil { + return nil, fmt.Errorf("nil client") + } + + opts, apiOpts := getOpts(opt...) + opts.queryMap["scope_id"] = scopeId + + req, err := c.client.NewRequest(ctx, "GET", "credential-stores", nil, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating List 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 List call: %w", err) + } + + target := new(CredentialStoreListResult) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, fmt.Errorf("error decoding List response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} diff --git a/api/credentialstores/option.gen.go b/api/credentialstores/option.gen.go new file mode 100644 index 000000000..60d32bbd2 --- /dev/null +++ b/api/credentialstores/option.gen.go @@ -0,0 +1,292 @@ +package credentialstores + +import ( + "strconv" + "strings" + + "github.com/hashicorp/boundary/api" +) + +// Option is a func that sets optional attributes for a call. This does not need +// to be used directly, but instead option arguments are built from the +// functions in this package. WithX options set a value to that given in the +// argument; DefaultX options indicate that the value should be set to its +// default. When an API call is made options are processed in ther order they +// appear in the function call, so for a given argument X, a succession of WithX +// or DefaultX calls will result in the last call taking effect. +type Option func(*options) + +type options struct { + postMap map[string]interface{} + queryMap map[string]string + withAutomaticVersioning bool + withSkipCurlOutput bool + withFilter string + withRecursive bool +} + +func getDefaultOptions() options { + return options{ + postMap: make(map[string]interface{}), + queryMap: make(map[string]string), + } +} + +func getOpts(opt ...Option) (options, []api.Option) { + opts := getDefaultOptions() + for _, o := range opt { + if o != nil { + o(&opts) + } + } + var apiOpts []api.Option + if opts.withSkipCurlOutput { + apiOpts = append(apiOpts, api.WithSkipCurlOutput(true)) + } + if opts.withFilter != "" { + opts.queryMap["filter"] = opts.withFilter + } + if opts.withRecursive { + opts.queryMap["recursive"] = strconv.FormatBool(opts.withRecursive) + } + return opts, apiOpts +} + +// If set, and if the version is zero during an update, the API will perform a +// fetch to get the current version of the resource and populate it during the +// update call. This is convenient but opens up the possibility for subtle +// order-of-modification issues, so use carefully. +func WithAutomaticVersioning(enable bool) Option { + return func(o *options) { + o.withAutomaticVersioning = enable + } +} + +// WithSkipCurlOutput tells the API to not use the current call for cURL output. +// Useful for when we need to look up versions. +func WithSkipCurlOutput(skip bool) Option { + return func(o *options) { + o.withSkipCurlOutput = true + } +} + +// WithFilter tells the API to filter the items returned using the provided +// filter term. The filter should be in a format supported by +// hashicorp/go-bexpr. +func WithFilter(filter string) Option { + return func(o *options) { + o.withFilter = strings.TrimSpace(filter) + } +} + +// WithRecursive tells the API to use recursion for listing operations on this +// resource +func WithRecursive(recurse bool) Option { + return func(o *options) { + o.withRecursive = true + } +} + +func WithVaultCredentialStoreAddress(inAddress string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["address"] = inAddress + o.postMap["attributes"] = val + } +} + +func WithAttributes(inAttributes map[string]interface{}) Option { + return func(o *options) { + o.postMap["attributes"] = inAttributes + } +} + +func DefaultAttributes() Option { + return func(o *options) { + o.postMap["attributes"] = nil + } +} + +func WithVaultCredentialStoreCaCert(inCaCert string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["ca_cert"] = inCaCert + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreCaCert() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["ca_cert"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialStoreClientCertificate(inClientCertificate string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["client_certificate"] = inClientCertificate + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreClientCertificate() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["client_certificate"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialStoreClientCertificateKey(inClientCertificateKey string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["client_certificate_key"] = inClientCertificateKey + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreClientCertificateKey() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["client_certificate_key"] = nil + o.postMap["attributes"] = val + } +} + +func WithDescription(inDescription string) Option { + return func(o *options) { + o.postMap["description"] = inDescription + } +} + +func DefaultDescription() Option { + return func(o *options) { + o.postMap["description"] = nil + } +} + +func WithName(inName string) Option { + return func(o *options) { + o.postMap["name"] = inName + } +} + +func DefaultName() Option { + return func(o *options) { + o.postMap["name"] = nil + } +} + +func WithVaultCredentialStoreNamespace(inNamespace string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["namespace"] = inNamespace + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreNamespace() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["namespace"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialStoreTlsServerName(inTlsServerName string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["tls_server_name"] = inTlsServerName + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreTlsServerName() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["tls_server_name"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialStoreTlsSkipVerify(inTlsSkipVerify bool) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["tls_skip_verify"] = inTlsSkipVerify + o.postMap["attributes"] = val + } +} + +func DefaultVaultCredentialStoreTlsSkipVerify() Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["tls_skip_verify"] = nil + o.postMap["attributes"] = val + } +} + +func WithVaultCredentialStoreToken(inToken string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["token"] = inToken + o.postMap["attributes"] = val + } +} diff --git a/api/credentialstores/vault_credential_store_attributes.gen.go b/api/credentialstores/vault_credential_store_attributes.gen.go new file mode 100644 index 000000000..81ce70dc9 --- /dev/null +++ b/api/credentialstores/vault_credential_store_attributes.gen.go @@ -0,0 +1,15 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentialstores + +type VaultCredentialStoreAttributes struct { + Address string `json:"address,omitempty"` + Namespace string `json:"namespace,omitempty"` + CaCert string `json:"ca_cert,omitempty"` + TlsServerName string `json:"tls_server_name,omitempty"` + TlsSkipVerify bool `json:"tls_skip_verify,omitempty"` + Token string `json:"token,omitempty"` + TokenHmac string `json:"token_hmac,omitempty"` + ClientCertificate string `json:"client_certificate,omitempty"` + ClientCertificateKey string `json:"client_certificate_key,omitempty"` + ClientCertificateKeyHmac string `json:"client_certificate_key_hmac,omitempty"` +} diff --git a/api/groups/group.gen.go b/api/groups/group.gen.go index e36662236..a29ba5eb1 100644 --- a/api/groups/group.gen.go +++ b/api/groups/group.gen.go @@ -134,9 +134,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Gr return target, nil } -func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*GroupReadResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*GroupReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -144,7 +144,7 @@ func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*Grou opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("groups/%s", groupId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("groups/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -175,9 +175,9 @@ func (c *Client) Read(ctx context.Context, groupId string, opt ...Option) (*Grou return target, nil } -func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt ...Option) (*GroupUpdateResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*GroupUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -189,7 +189,7 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -207,7 +207,7 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("groups/%s", groupId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("groups/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -238,9 +238,9 @@ func (c *Client) Update(ctx context.Context, groupId string, version uint32, opt return target, nil } -func (c *Client) Delete(ctx context.Context, groupId string, opt ...Option) (*GroupDeleteResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*GroupDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -248,7 +248,7 @@ func (c *Client) Delete(ctx context.Context, groupId string, opt ...Option) (*Gr opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("groups/%s", groupId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("groups/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } @@ -321,9 +321,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Grou return target, nil } -func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into AddMembers request") +func (c *Client) AddMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddMembers request") } if len(memberIds) == 0 { return nil, errors.New("empty memberIds passed into AddMembers request") @@ -338,7 +338,7 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddMembers request") } - existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -357,7 +357,7 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32, opts.postMap["version"] = version opts.postMap["member_ids"] = memberIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:add-members", groupId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:add-members", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddMembers request: %w", err) } @@ -388,9 +388,9 @@ func (c *Client) AddMembers(ctx context.Context, groupId string, version uint32, return target, nil } -func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into SetMembers request") +func (c *Client) SetMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetMembers request") } if c.client == nil { @@ -403,7 +403,7 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetMembers request") } - existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -422,7 +422,7 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32, opts.postMap["version"] = version opts.postMap["member_ids"] = memberIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:set-members", groupId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:set-members", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetMembers request: %w", err) } @@ -453,9 +453,9 @@ func (c *Client) SetMembers(ctx context.Context, groupId string, version uint32, return target, nil } -func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { - if groupId == "" { - return nil, fmt.Errorf("empty groupId value passed into RemoveMembers request") +func (c *Client) RemoveMembers(ctx context.Context, id string, version uint32, memberIds []string, opt ...Option) (*GroupUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveMembers request") } if len(memberIds) == 0 { return nil, errors.New("empty memberIds passed into RemoveMembers request") @@ -470,7 +470,7 @@ func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemoveMembers request") } - existingTarget, existingErr := c.Read(ctx, groupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -489,7 +489,7 @@ func (c *Client) RemoveMembers(ctx context.Context, groupId string, version uint opts.postMap["version"] = version opts.postMap["member_ids"] = memberIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:remove-members", groupId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("groups/%s:remove-members", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemoveMembers request: %w", err) } diff --git a/api/hostcatalogs/host_catalog.gen.go b/api/hostcatalogs/host_catalog.gen.go index f047e62f8..ffa8f3856 100644 --- a/api/hostcatalogs/host_catalog.gen.go +++ b/api/hostcatalogs/host_catalog.gen.go @@ -140,9 +140,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string return target, nil } -func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option) (*HostCatalogReadResult, error) { - if hostCatalogId == "" { - return nil, fmt.Errorf("empty hostCatalogId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostCatalogReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -150,7 +150,7 @@ func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option) opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-catalogs/%s", hostCatalogId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-catalogs/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -181,9 +181,9 @@ func (c *Client) Read(ctx context.Context, hostCatalogId string, opt ...Option) return target, nil } -func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint32, opt ...Option) (*HostCatalogUpdateResult, error) { - if hostCatalogId == "" { - return nil, fmt.Errorf("empty hostCatalogId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostCatalogUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -195,7 +195,7 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, hostCatalogId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -213,7 +213,7 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3 opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-catalogs/%s", hostCatalogId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-catalogs/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -244,9 +244,9 @@ func (c *Client) Update(ctx context.Context, hostCatalogId string, version uint3 return target, nil } -func (c *Client) Delete(ctx context.Context, hostCatalogId string, opt ...Option) (*HostCatalogDeleteResult, error) { - if hostCatalogId == "" { - return nil, fmt.Errorf("empty hostCatalogId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostCatalogDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -254,7 +254,7 @@ func (c *Client) Delete(ctx context.Context, hostCatalogId string, opt ...Option opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-catalogs/%s", hostCatalogId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-catalogs/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/hosts/host.gen.go b/api/hosts/host.gen.go index d0545890c..51b6b424a 100644 --- a/api/hosts/host.gen.go +++ b/api/hosts/host.gen.go @@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, hostCatalogId string, opt ...Option return target, nil } -func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostReadResult, error) { - if hostId == "" { - return nil, fmt.Errorf("empty hostId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostR opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("hosts/%s", hostId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("hosts/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, hostId string, opt ...Option) (*HostR return target, nil } -func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt ...Option) (*HostUpdateResult, error) { - if hostId == "" { - return nil, fmt.Errorf("empty hostId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, hostId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("hosts/%s", hostId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("hosts/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, hostId string, version uint32, opt return target, nil } -func (c *Client) Delete(ctx context.Context, hostId string, opt ...Option) (*HostDeleteResult, error) { - if hostId == "" { - return nil, fmt.Errorf("empty hostId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, hostId string, opt ...Option) (*Hos opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("hosts/%s", hostId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("hosts/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/hostsets/host_set.gen.go b/api/hostsets/host_set.gen.go index 96184941f..9c27127ff 100644 --- a/api/hostsets/host_set.gen.go +++ b/api/hostsets/host_set.gen.go @@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, hostCatalogId string, opt ...Option return target, nil } -func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*HostSetReadResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*HostSetReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*Ho opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("host-sets/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, hostSetId string, opt ...Option) (*Ho return target, nil } -func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, opt ...Option) (*HostSetUpdateResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*HostSetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-sets/%s", hostSetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("host-sets/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, hostSetId string, version uint32, o return target, nil } -func (c *Client) Delete(ctx context.Context, hostSetId string, opt ...Option) (*HostSetDeleteResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*HostSetDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, hostSetId string, opt ...Option) (* opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-sets/%s", hostSetId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("host-sets/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } @@ -322,9 +322,9 @@ func (c *Client) List(ctx context.Context, hostCatalogId string, opt ...Option) return target, nil } -func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into AddHosts request") +func (c *Client) AddHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddHosts request") } if len(hostIds) == 0 { return nil, errors.New("empty hostIds passed into AddHosts request") @@ -339,7 +339,7 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddHosts request") } - existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -358,7 +358,7 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32, opts.postMap["version"] = version opts.postMap["host_ids"] = hostIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:add-hosts", hostSetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:add-hosts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddHosts request: %w", err) } @@ -389,9 +389,9 @@ func (c *Client) AddHosts(ctx context.Context, hostSetId string, version uint32, return target, nil } -func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into SetHosts request") +func (c *Client) SetHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetHosts request") } if c.client == nil { @@ -404,7 +404,7 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetHosts request") } - existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -423,7 +423,7 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32, opts.postMap["version"] = version opts.postMap["host_ids"] = hostIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:set-hosts", hostSetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:set-hosts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetHosts request: %w", err) } @@ -454,9 +454,9 @@ func (c *Client) SetHosts(ctx context.Context, hostSetId string, version uint32, return target, nil } -func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { - if hostSetId == "" { - return nil, fmt.Errorf("empty hostSetId value passed into RemoveHosts request") +func (c *Client) RemoveHosts(ctx context.Context, id string, version uint32, hostIds []string, opt ...Option) (*HostSetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveHosts request") } if len(hostIds) == 0 { return nil, errors.New("empty hostIds passed into RemoveHosts request") @@ -471,7 +471,7 @@ func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemoveHosts request") } - existingTarget, existingErr := c.Read(ctx, hostSetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -490,7 +490,7 @@ func (c *Client) RemoveHosts(ctx context.Context, hostSetId string, version uint opts.postMap["version"] = version opts.postMap["host_ids"] = hostIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:remove-hosts", hostSetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("host-sets/%s:remove-hosts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemoveHosts request: %w", err) } diff --git a/api/managedgroups/managedgroups.gen.go b/api/managedgroups/managedgroups.gen.go index 94741999b..a79f3ccc5 100644 --- a/api/managedgroups/managedgroups.gen.go +++ b/api/managedgroups/managedgroups.gen.go @@ -135,9 +135,9 @@ func (c *Client) Create(ctx context.Context, authMethodId string, opt ...Option) return target, nil } -func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupReadResult, error) { - if managedGroupId == "" { - return nil, fmt.Errorf("empty managedGroupId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*ManagedGroupReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -145,7 +145,7 @@ func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option) opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("managed-groups/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -176,9 +176,9 @@ func (c *Client) Read(ctx context.Context, managedGroupId string, opt ...Option) return target, nil } -func (c *Client) Update(ctx context.Context, managedGroupId string, version uint32, opt ...Option) (*ManagedGroupUpdateResult, error) { - if managedGroupId == "" { - return nil, fmt.Errorf("empty managedGroupId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*ManagedGroupUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -190,7 +190,7 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, managedGroupId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -208,7 +208,7 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("managed-groups/%s", managedGroupId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("managed-groups/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -239,9 +239,9 @@ func (c *Client) Update(ctx context.Context, managedGroupId string, version uint return target, nil } -func (c *Client) Delete(ctx context.Context, managedGroupId string, opt ...Option) (*ManagedGroupDeleteResult, error) { - if managedGroupId == "" { - return nil, fmt.Errorf("empty managedGroupId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*ManagedGroupDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -249,7 +249,7 @@ func (c *Client) Delete(ctx context.Context, managedGroupId string, opt ...Optio opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("managed-groups/%s", managedGroupId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("managed-groups/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/roles/role.gen.go b/api/roles/role.gen.go index 486e01ac7..ea64d5405 100644 --- a/api/roles/role.gen.go +++ b/api/roles/role.gen.go @@ -137,9 +137,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Ro return target, nil } -func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleReadResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*RoleReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -147,7 +147,7 @@ func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleR opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("roles/%s", roleId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("roles/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -178,9 +178,9 @@ func (c *Client) Read(ctx context.Context, roleId string, opt ...Option) (*RoleR return target, nil } -func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -192,7 +192,7 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -210,7 +210,7 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("roles/%s", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("roles/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -241,9 +241,9 @@ func (c *Client) Update(ctx context.Context, roleId string, version uint32, opt return target, nil } -func (c *Client) Delete(ctx context.Context, roleId string, opt ...Option) (*RoleDeleteResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*RoleDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -251,7 +251,7 @@ func (c *Client) Delete(ctx context.Context, roleId string, opt ...Option) (*Rol opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("roles/%s", roleId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("roles/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } @@ -324,9 +324,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Role return target, nil } -func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into AddGrants request") +func (c *Client) AddGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddGrants request") } if len(grantStrings) == 0 { return nil, errors.New("empty grantStrings passed into AddGrants request") @@ -341,7 +341,7 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddGrants request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -360,7 +360,7 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g opts.postMap["version"] = version opts.postMap["grant_strings"] = grantStrings - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grants", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-grants", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddGrants request: %w", err) } @@ -391,9 +391,9 @@ func (c *Client) AddGrants(ctx context.Context, roleId string, version uint32, g return target, nil } -func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into AddPrincipals request") +func (c *Client) AddPrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddPrincipals request") } if len(principalIds) == 0 { return nil, errors.New("empty principalIds passed into AddPrincipals request") @@ -408,7 +408,7 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddPrincipals request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -427,7 +427,7 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3 opts.postMap["version"] = version opts.postMap["principal_ids"] = principalIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-principals", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:add-principals", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddPrincipals request: %w", err) } @@ -458,9 +458,9 @@ func (c *Client) AddPrincipals(ctx context.Context, roleId string, version uint3 return target, nil } -func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into SetGrants request") +func (c *Client) SetGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetGrants request") } if c.client == nil { @@ -473,7 +473,7 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetGrants request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -492,7 +492,7 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g opts.postMap["version"] = version opts.postMap["grant_strings"] = grantStrings - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grants", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-grants", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetGrants request: %w", err) } @@ -523,9 +523,9 @@ func (c *Client) SetGrants(ctx context.Context, roleId string, version uint32, g return target, nil } -func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into SetPrincipals request") +func (c *Client) SetPrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetPrincipals request") } if c.client == nil { @@ -538,7 +538,7 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetPrincipals request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -557,7 +557,7 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3 opts.postMap["version"] = version opts.postMap["principal_ids"] = principalIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-principals", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:set-principals", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetPrincipals request: %w", err) } @@ -588,9 +588,9 @@ func (c *Client) SetPrincipals(ctx context.Context, roleId string, version uint3 return target, nil } -func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into RemoveGrants request") +func (c *Client) RemoveGrants(ctx context.Context, id string, version uint32, grantStrings []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveGrants request") } if len(grantStrings) == 0 { return nil, errors.New("empty grantStrings passed into RemoveGrants request") @@ -605,7 +605,7 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemoveGrants request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -624,7 +624,7 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32 opts.postMap["version"] = version opts.postMap["grant_strings"] = grantStrings - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grants", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-grants", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemoveGrants request: %w", err) } @@ -655,9 +655,9 @@ func (c *Client) RemoveGrants(ctx context.Context, roleId string, version uint32 return target, nil } -func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { - if roleId == "" { - return nil, fmt.Errorf("empty roleId value passed into RemovePrincipals request") +func (c *Client) RemovePrincipals(ctx context.Context, id string, version uint32, principalIds []string, opt ...Option) (*RoleUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemovePrincipals request") } if len(principalIds) == 0 { return nil, errors.New("empty principalIds passed into RemovePrincipals request") @@ -672,7 +672,7 @@ func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version ui if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemovePrincipals request") } - existingTarget, existingErr := c.Read(ctx, roleId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -691,7 +691,7 @@ func (c *Client) RemovePrincipals(ctx context.Context, roleId string, version ui opts.postMap["version"] = version opts.postMap["principal_ids"] = principalIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-principals", roleId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("roles/%s:remove-principals", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemovePrincipals request: %w", err) } diff --git a/api/scopes/scope.gen.go b/api/scopes/scope.gen.go index 1f34be548..ae11d826c 100644 --- a/api/scopes/scope.gen.go +++ b/api/scopes/scope.gen.go @@ -134,9 +134,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Sc return target, nil } -func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*ScopeReadResult, error) { - if scopeId == "" { - return nil, fmt.Errorf("empty scopeId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*ScopeReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -144,7 +144,7 @@ func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*Scop opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("scopes/%s", scopeId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("scopes/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -175,9 +175,9 @@ func (c *Client) Read(ctx context.Context, scopeId string, opt ...Option) (*Scop return target, nil } -func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt ...Option) (*ScopeUpdateResult, error) { - if scopeId == "" { - return nil, fmt.Errorf("empty scopeId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*ScopeUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -189,7 +189,7 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, scopeId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -207,7 +207,7 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("scopes/%s", scopeId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("scopes/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -238,9 +238,9 @@ func (c *Client) Update(ctx context.Context, scopeId string, version uint32, opt return target, nil } -func (c *Client) Delete(ctx context.Context, scopeId string, opt ...Option) (*ScopeDeleteResult, error) { - if scopeId == "" { - return nil, fmt.Errorf("empty scopeId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*ScopeDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -248,7 +248,7 @@ func (c *Client) Delete(ctx context.Context, scopeId string, opt ...Option) (*Sc opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("scopes/%s", scopeId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("scopes/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } diff --git a/api/sessions/session.gen.go b/api/sessions/session.gen.go index 59e4e7abc..f9b5cdda4 100644 --- a/api/sessions/session.gen.go +++ b/api/sessions/session.gen.go @@ -98,9 +98,9 @@ func (c *Client) ApiClient() *api.Client { return c.client } -func (c *Client) Read(ctx context.Context, sessionId string, opt ...Option) (*SessionReadResult, error) { - if sessionId == "" { - return nil, fmt.Errorf("empty sessionId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*SessionReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -108,7 +108,7 @@ func (c *Client) Read(ctx context.Context, sessionId string, opt ...Option) (*Se opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("sessions/%s", sessionId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("sessions/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } diff --git a/api/targets/credential_library.gen.go b/api/targets/credential_library.gen.go new file mode 100644 index 000000000..5f5d623bf --- /dev/null +++ b/api/targets/credential_library.gen.go @@ -0,0 +1,11 @@ +// Code generated by "make api"; DO NOT EDIT. +package targets + +type CredentialLibrary struct { + Id string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + CredentialStoreId string `json:"credential_store_id,omitempty"` + Purpose string `json:"purpose,omitempty"` + Type string `json:"type,omitempty"` +} diff --git a/api/targets/session_authorization.gen.go b/api/targets/session_authorization.gen.go index a58ff2329..61d877fae 100644 --- a/api/targets/session_authorization.gen.go +++ b/api/targets/session_authorization.gen.go @@ -8,14 +8,15 @@ import ( ) type SessionAuthorization struct { - SessionId string `json:"session_id,omitempty"` - TargetId string `json:"target_id,omitempty"` - Scope *scopes.ScopeInfo `json:"scope,omitempty"` - CreatedTime time.Time `json:"created_time,omitempty"` - UserId string `json:"user_id,omitempty"` - HostSetId string `json:"host_set_id,omitempty"` - HostId string `json:"host_id,omitempty"` - Type string `json:"type,omitempty"` - AuthorizationToken string `json:"authorization_token,omitempty"` - Endpoint string `json:"endpoint,omitempty"` + SessionId string `json:"session_id,omitempty"` + TargetId string `json:"target_id,omitempty"` + Scope *scopes.ScopeInfo `json:"scope,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + UserId string `json:"user_id,omitempty"` + HostSetId string `json:"host_set_id,omitempty"` + HostId string `json:"host_id,omitempty"` + Type string `json:"type,omitempty"` + AuthorizationToken string `json:"authorization_token,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + Credentials []*SessionCredential `json:"credentials,omitempty"` } diff --git a/api/targets/session_credential.gen.go b/api/targets/session_credential.gen.go new file mode 100644 index 000000000..d87998cdb --- /dev/null +++ b/api/targets/session_credential.gen.go @@ -0,0 +1,7 @@ +// Code generated by "make api"; DO NOT EDIT. +package targets + +type SessionCredential struct { + CredentialLibrary *CredentialLibrary `json:"credential_library,omitempty"` + Secret string `json:"secret,omitempty"` +} diff --git a/api/targets/target.gen.go b/api/targets/target.gen.go index 6acf813a7..de01afdb6 100644 --- a/api/targets/target.gen.go +++ b/api/targets/target.gen.go @@ -27,6 +27,8 @@ type Target struct { SessionMaxSeconds uint32 `json:"session_max_seconds,omitempty"` SessionConnectionLimit int32 `json:"session_connection_limit,omitempty"` WorkerFilter string `json:"worker_filter,omitempty"` + CredentialLibraryIds []string `json:"credential_library_ids,omitempty"` + CredentialLibraries []*CredentialLibrary `json:"credential_libraries,omitempty"` Attributes map[string]interface{} `json:"attributes,omitempty"` AuthorizedActions []string `json:"authorized_actions,omitempty"` @@ -144,9 +146,9 @@ func (c *Client) Create(ctx context.Context, resourceType string, scopeId string return target, nil } -func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*TargetReadResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*TargetReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -154,7 +156,7 @@ func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*Tar opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("targets/%s", targetId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("targets/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -185,9 +187,9 @@ func (c *Client) Read(ctx context.Context, targetId string, opt ...Option) (*Tar return target, nil } -func (c *Client) Update(ctx context.Context, targetId string, version uint32, opt ...Option) (*TargetUpdateResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -199,7 +201,7 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -217,7 +219,7 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("targets/%s", targetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("targets/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -248,9 +250,9 @@ func (c *Client) Update(ctx context.Context, targetId string, version uint32, op return target, nil } -func (c *Client) Delete(ctx context.Context, targetId string, opt ...Option) (*TargetDeleteResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*TargetDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -258,7 +260,7 @@ func (c *Client) Delete(ctx context.Context, targetId string, opt ...Option) (*T opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("targets/%s", targetId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("targets/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } @@ -331,9 +333,76 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*Targ return target, nil } -func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into AddHostSets request") +func (c *Client) AddCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddCredentialLibraries request") + } + if len(credentialLibraryIds) == 0 { + return nil, errors.New("empty credentialLibraryIds passed into AddCredentialLibraries request") + } + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into AddCredentialLibraries request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + opts.postMap["credential_library_ids"] = credentialLibraryIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-credential-libraries", id), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating AddCredentialLibraries 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 AddCredentialLibraries call: %w", err) + } + + target := new(TargetUpdateResult) + target.Item = new(Target) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding AddCredentialLibraries response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) AddHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddHostSets request") } if len(hostSetIds) == 0 { return nil, errors.New("empty hostSetIds passed into AddHostSets request") @@ -348,7 +417,7 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddHostSets request") } - existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -367,7 +436,7 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3 opts.postMap["version"] = version opts.postMap["host_set_ids"] = hostSetIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-host-sets", targetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:add-host-sets", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddHostSets request: %w", err) } @@ -398,9 +467,74 @@ func (c *Client) AddHostSets(ctx context.Context, targetId string, version uint3 return target, nil } -func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into SetHostSets request") +func (c *Client) SetCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetCredentialLibraries request") + } + + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into SetCredentialLibraries request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + opts.postMap["credential_library_ids"] = credentialLibraryIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-credential-libraries", id), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating SetCredentialLibraries 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 SetCredentialLibraries call: %w", err) + } + + target := new(TargetUpdateResult) + target.Item = new(Target) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding SetCredentialLibraries response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) SetHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetHostSets request") } if c.client == nil { @@ -413,7 +547,7 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3 if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetHostSets request") } - existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -432,7 +566,7 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3 opts.postMap["version"] = version opts.postMap["host_set_ids"] = hostSetIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-host-sets", targetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:set-host-sets", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetHostSets request: %w", err) } @@ -463,9 +597,76 @@ func (c *Client) SetHostSets(ctx context.Context, targetId string, version uint3 return target, nil } -func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { - if targetId == "" { - return nil, fmt.Errorf("empty targetId value passed into RemoveHostSets request") +func (c *Client) RemoveCredentialLibraries(ctx context.Context, id string, version uint32, credentialLibraryIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveCredentialLibraries request") + } + if len(credentialLibraryIds) == 0 { + return nil, errors.New("empty credentialLibraryIds passed into RemoveCredentialLibraries request") + } + if c.client == nil { + return nil, errors.New("nil client") + } + + opts, apiOpts := getOpts(opt...) + + if version == 0 { + if !opts.withAutomaticVersioning { + return nil, errors.New("zero version number passed into RemoveCredentialLibraries request") + } + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + if existingErr != nil { + if api.AsServerError(existingErr) != nil { + return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) + } + return nil, fmt.Errorf("error performing initial check-and-set read: %w", existingErr) + } + if existingTarget == nil { + return nil, errors.New("nil resource response found when performing initial check-and-set read") + } + if existingTarget.Item == nil { + return nil, errors.New("nil resource found when performing initial check-and-set read") + } + version = existingTarget.Item.Version + } + + opts.postMap["version"] = version + opts.postMap["credential_library_ids"] = credentialLibraryIds + + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-credential-libraries", id), opts.postMap, apiOpts...) + if err != nil { + return nil, fmt.Errorf("error creating RemoveCredentialLibraries 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 RemoveCredentialLibraries call: %w", err) + } + + target := new(TargetUpdateResult) + target.Item = new(Target) + apiErr, err := resp.Decode(target.Item) + if err != nil { + return nil, fmt.Errorf("error decoding RemoveCredentialLibraries response: %w", err) + } + if apiErr != nil { + return nil, apiErr + } + target.response = resp + return target, nil +} + +func (c *Client) RemoveHostSets(ctx context.Context, id string, version uint32, hostSetIds []string, opt ...Option) (*TargetUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveHostSets request") } if len(hostSetIds) == 0 { return nil, errors.New("empty hostSetIds passed into RemoveHostSets request") @@ -480,7 +681,7 @@ func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version ui if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemoveHostSets request") } - existingTarget, existingErr := c.Read(ctx, targetId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -499,7 +700,7 @@ func (c *Client) RemoveHostSets(ctx context.Context, targetId string, version ui opts.postMap["version"] = version opts.postMap["host_set_ids"] = hostSetIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-host-sets", targetId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("targets/%s:remove-host-sets", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemoveHostSets request: %w", err) } diff --git a/api/users/user.gen.go b/api/users/user.gen.go index 1ab9ace25..69bce4ea3 100644 --- a/api/users/user.gen.go +++ b/api/users/user.gen.go @@ -138,9 +138,9 @@ func (c *Client) Create(ctx context.Context, scopeId string, opt ...Option) (*Us return target, nil } -func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserReadResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt ...Option) (*UserReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -148,7 +148,7 @@ func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserR opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("users/%s", userId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("users/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Read request: %w", err) } @@ -179,9 +179,9 @@ func (c *Client) Read(ctx context.Context, userId string, opt ...Option) (*UserR return target, nil } -func (c *Client) Update(ctx context.Context, userId string, version uint32, opt ...Option) (*UserUpdateResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt ...Option) (*UserUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -193,7 +193,7 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -211,7 +211,7 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt opts.postMap["version"] = version - req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("users/%s", userId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "PATCH", fmt.Sprintf("users/%s", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Update request: %w", err) } @@ -242,9 +242,9 @@ func (c *Client) Update(ctx context.Context, userId string, version uint32, opt return target, nil } -func (c *Client) Delete(ctx context.Context, userId string, opt ...Option) (*UserDeleteResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt ...Option) (*UserDeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -252,7 +252,7 @@ func (c *Client) Delete(ctx context.Context, userId string, opt ...Option) (*Use opts, apiOpts := getOpts(opt...) - req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("users/%s", userId), nil, apiOpts...) + req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("users/%s", id), nil, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating Delete request: %w", err) } @@ -325,9 +325,9 @@ func (c *Client) List(ctx context.Context, scopeId string, opt ...Option) (*User return target, nil } -func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into AddAccounts request") +func (c *Client) AddAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into AddAccounts request") } if len(accountIds) == 0 { return nil, errors.New("empty accountIds passed into AddAccounts request") @@ -342,7 +342,7 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into AddAccounts request") } - existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -361,7 +361,7 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32, opts.postMap["version"] = version opts.postMap["account_ids"] = accountIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:add-accounts", userId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:add-accounts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating AddAccounts request: %w", err) } @@ -392,9 +392,9 @@ func (c *Client) AddAccounts(ctx context.Context, userId string, version uint32, return target, nil } -func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into SetAccounts request") +func (c *Client) SetAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into SetAccounts request") } if c.client == nil { @@ -407,7 +407,7 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into SetAccounts request") } - existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -426,7 +426,7 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32, opts.postMap["version"] = version opts.postMap["account_ids"] = accountIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:set-accounts", userId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:set-accounts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating SetAccounts request: %w", err) } @@ -457,9 +457,9 @@ func (c *Client) SetAccounts(ctx context.Context, userId string, version uint32, return target, nil } -func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { - if userId == "" { - return nil, fmt.Errorf("empty userId value passed into RemoveAccounts request") +func (c *Client) RemoveAccounts(ctx context.Context, id string, version uint32, accountIds []string, opt ...Option) (*UserUpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into RemoveAccounts request") } if len(accountIds) == 0 { return nil, errors.New("empty accountIds passed into RemoveAccounts request") @@ -474,7 +474,7 @@ func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into RemoveAccounts request") } - existingTarget, existingErr := c.Read(ctx, userId, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -493,7 +493,7 @@ func (c *Client) RemoveAccounts(ctx context.Context, userId string, version uint opts.postMap["version"] = version opts.postMap["account_ids"] = accountIds - req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:remove-accounts", userId), opts.postMap, apiOpts...) + req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("users/%s:remove-accounts", id), opts.postMap, apiOpts...) if err != nil { return nil, fmt.Errorf("error creating RemoveAccounts request: %w", err) } diff --git a/globals/fields.go b/globals/fields.go index 5a0cb1bfa..30023ff8a 100644 --- a/globals/fields.go +++ b/globals/fields.go @@ -51,4 +51,7 @@ const ( EmailField = "email" ManagedGroupIdsField = "managed_group_ids" FilterField = "filter" + CredentialStoreIdField = "credential_store_id" + CredentialLibraryIdsField = "credential_library_ids" + CredentialLibrariesField = "credential_libraries" ) diff --git a/go.mod b/go.mod index bfce89294..d255549aa 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,11 @@ require ( github.com/hashicorp/go-kms-wrapping v0.6.2 github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-retryablehttp v0.7.0 + github.com/hashicorp/go-rootcerts v1.0.2 github.com/hashicorp/go-uuid v1.0.2 github.com/hashicorp/hcl v1.0.0 github.com/hashicorp/shared-secure-libs v0.0.6 + github.com/hashicorp/vault/api v1.1.0 github.com/hashicorp/vault/sdk v0.2.0 github.com/iancoleman/strcase v0.1.3 github.com/jefferai/keyring v1.1.7-0.20210105022822-8749b3d9ce79 diff --git a/go.sum b/go.sum index cf66d7f4d..bc5a22a0a 100644 --- a/go.sum +++ b/go.sum @@ -521,8 +521,9 @@ github.com/hashicorp/shared-secure-libs v0.0.6 h1:QH22a9VbBP3b7v/e5In7uHTanimGPz github.com/hashicorp/shared-secure-libs v0.0.6/go.mod h1:PH51kNtHA6kYV5Z8E3TOJM634iyp7IIxAMfLh0Dp35o= github.com/hashicorp/vault/api v1.0.5-0.20200514164214-89b1987e38c2/go.mod h1:P8A7gn1a6j/7qQ4zOaWI6FC/Q9jQWEBRTOwZH33tC9o= github.com/hashicorp/vault/api v1.0.5-0.20200519221902-385fac77e20f/go.mod h1:euTFbi2YJgwcju3imEt919lhJKF68nN1cQPq3aA+kBE= -github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6 h1:ChHpobQse3kjVX7GmbpUFVqaMTf4lb1gcLrJltsUmS4= github.com/hashicorp/vault/api v1.0.5-0.20200805123347-1ef507638af6/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= +github.com/hashicorp/vault/api v1.1.0 h1:QcxC7FuqEl0sZaIjcXB/kNEeBa0DH5z57qbWBvZwLC4= +github.com/hashicorp/vault/api v1.1.0/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= github.com/hashicorp/vault/sdk v0.1.14-0.20200514144402-4bfac290c352/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= github.com/hashicorp/vault/sdk v0.1.14-0.20200514164214-89b1987e38c2/go.mod h1:WX57W2PwkrOPQ6rVQk+dy5/htHIaB4aBM70EwKThu10= diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index 060c9c6e4..2d115acba 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -7,6 +7,8 @@ import ( "github.com/hashicorp/boundary/internal/gen/controller/api/resources/accounts" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authmethods" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/authtokens" + "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries" + "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/groups" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hostcatalogs" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/hosts" @@ -67,13 +69,8 @@ type structInfo struct { // fields. versionEnabled bool - // The parameters passed into the path. These should be non-pluralized resource names. - // The templates will convert '-' to '_' and append an _id to them in the SDK param - // and append an 's' to it when building the path. - // The final value should be the path name of the resource since for single resource - // operations all values are used for the function argument. - // For collection based operations the last value is ignored for generating function argument. - pathArgs []string + // This is used for building the api path. + pluralResourceName string // typeOnCreate indicates that create will be creating a concrete // implementation of an abstract type and thus a type field is necessary @@ -138,7 +135,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"scope"}, + pluralResourceName: "scopes", extraOptions: []fieldInfo{ { Name: "SkipAdminRoleCreation", @@ -177,7 +174,7 @@ var inputStructs = []*structInfo{ sliceSubtypes: map[string]string{ "Accounts": "accountIds", }, - pathArgs: []string{"user"}, + pluralResourceName: "users", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -202,7 +199,7 @@ var inputStructs = []*structInfo{ sliceSubtypes: map[string]string{ "Members": "memberIds", }, - pathArgs: []string{"group"}, + pluralResourceName: "groups", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -238,7 +235,7 @@ var inputStructs = []*structInfo{ "Principals": "principalIds", "Grants": "grantStrings", }, - pathArgs: []string{"role"}, + pluralResourceName: "roles", versionEnabled: true, createResponseTypes: true, recursiveListing: true, @@ -270,7 +267,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"auth-method"}, + pluralResourceName: "auth-methods", typeOnCreate: true, versionEnabled: true, createResponseTypes: true, @@ -298,7 +295,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"account"}, + pluralResourceName: "accounts", parentTypeName: "auth-method", versionEnabled: true, createResponseTypes: true, @@ -326,7 +323,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"managed-group"}, + pluralResourceName: "managed-groups", parentTypeName: "auth-method", versionEnabled: true, createResponseTypes: true, @@ -341,10 +338,71 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"auth-token"}, + pluralResourceName: "auth-tokens", createResponseTypes: true, recursiveListing: true, }, + // Credentials + { + inProto: &credentialstores.VaultCredentialStoreAttributes{}, + outFile: "credentialstores/vault_credential_store_attributes.gen.go", + subtypeName: "VaultCredentialStore", + }, + { + inProto: &credentialstores.CredentialStore{}, + outFile: "credentialstores/credential_store.gen.go", + templates: []*template.Template{ + clientTemplate, + createTemplate, + readTemplate, + updateTemplate, + deleteTemplate, + listTemplate, + }, + pluralResourceName: "credential-stores", + parentTypeName: "scope", + typeOnCreate: true, + versionEnabled: true, + createResponseTypes: true, + recursiveListing: true, + fieldOverrides: []fieldInfo{ + { + Name: "Address", + SkipDefault: true, + }, + { + Name: "Token", + SkipDefault: true, + }, + }, + }, + { + inProto: &credentiallibraries.VaultCredentialLibraryAttributes{}, + outFile: "credentiallibraries/vault_credential_library_attributes.gen.go", + subtypeName: "VaultCredentialLibrary", + fieldOverrides: []fieldInfo{ + { + Name: "Path", + SkipDefault: true, + }, + }, + }, + { + inProto: &credentiallibraries.CredentialLibrary{}, + outFile: "credentiallibraries/credential_library.gen.go", + templates: []*template.Template{ + clientTemplate, + createTemplate, + readTemplate, + updateTemplate, + deleteTemplate, + listTemplate, + }, + pluralResourceName: "credential-libraries", + parentTypeName: "credential-store", + versionEnabled: true, + createResponseTypes: true, + }, // Host related resources { inProto: &hostcatalogs.HostCatalog{}, @@ -357,7 +415,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"host-catalog"}, + pluralResourceName: "host-catalogs", typeOnCreate: true, versionEnabled: true, createResponseTypes: true, @@ -374,7 +432,7 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"host"}, + pluralResourceName: "hosts", parentTypeName: "host-catalog", versionEnabled: true, createResponseTypes: true, @@ -395,8 +453,8 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"host-set"}, - parentTypeName: "host-catalog", + pluralResourceName: "host-sets", + parentTypeName: "host-catalog", sliceSubtypes: map[string]string{ "Hosts": "hostIds", }, @@ -407,6 +465,14 @@ var inputStructs = []*structInfo{ inProto: &targets.HostSet{}, outFile: "targets/host_set.gen.go", }, + { + inProto: &targets.CredentialLibrary{}, + outFile: "targets/credential_library.gen.go", + }, + { + inProto: &targets.SessionCredential{}, + outFile: "targets/session_credential.gen.go", + }, { inProto: &targets.SessionAuthorization{}, outFile: "targets/session_authorization.gen.go", @@ -433,9 +499,10 @@ var inputStructs = []*structInfo{ deleteTemplate, listTemplate, }, - pathArgs: []string{"target"}, + pluralResourceName: "targets", sliceSubtypes: map[string]string{ - "HostSets": "hostSetIds", + "HostSets": "hostSetIds", + "CredentialLibraries": "credentialLibraryIds", }, extraOptions: []fieldInfo{ { @@ -478,7 +545,7 @@ var inputStructs = []*structInfo{ readTemplate, listTemplate, }, - pathArgs: []string{"session"}, + pluralResourceName: "sessions", createResponseTypes: true, fieldFilter: []string{"private_key"}, recursiveListing: true, diff --git a/internal/api/genapi/parse.go b/internal/api/genapi/parse.go index 6df15f5fb..c1a4f0d4c 100644 --- a/internal/api/genapi/parse.go +++ b/internal/api/genapi/parse.go @@ -99,6 +99,7 @@ var ( int32ValueName = (&wrapperspb.Int32Value{}).ProtoReflect().Descriptor().FullName() structValueName = (&_struct.Struct{}).ProtoReflect().Descriptor().FullName() timestampName = (×tamppb.Timestamp{}).ProtoReflect().Descriptor().FullName() + valueName = (&_struct.Value{}).ProtoReflect().Descriptor().FullName() ) func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) { @@ -113,6 +114,8 @@ func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) { return "", "", "int32" case structValueName: return "", "", "map[string]interface{}" + case valueName: + return "", "", "interface{}" case timestampName: return "", "time", "Time" default: diff --git a/internal/api/genapi/templates.go b/internal/api/genapi/templates.go index 2429ff6ea..a241fc4f0 100644 --- a/internal/api/genapi/templates.go +++ b/internal/api/genapi/templates.go @@ -13,23 +13,23 @@ import ( "github.com/iancoleman/strcase" ) -func getArgsAndPaths(in []string, parentTypeName, action string) (colArg, resArg string, colPath, resPath string) { - resArg = fmt.Sprintf("%sId", strcase.ToLowerCamel(strings.ReplaceAll(in[len(in)-1], "-", "_"))) +func getArgsAndPaths(pluralResource, parentTypeName, action string) (colArg, colPath, resPath string) { strToReplace := "scope" - if len(in) == 1 { - if parentTypeName != "" { - strToReplace = parentTypeName - } - } else { - strToReplace = in[len(in)-2] + if parentTypeName != "" { + strToReplace = parentTypeName } + colArg = fmt.Sprintf("%sId", strcase.ToLowerCamel(strings.ReplaceAll(strToReplace, "-", "_"))) - colPath = fmt.Sprintf("%ss", in[len(in)-1]) + colPath = pluralResource + // append s at the end only if it isn't already present + if colPath[len(colPath)-1] != 's' { + colPath = fmt.Sprintf("%ss", colPath) + } if action != "" { action = fmt.Sprintf(":%s", action) } - resPath = fmt.Sprintf("fmt.Sprintf(\"%s/%%s%s\", %s)", colPath, action, resArg) + resPath = fmt.Sprintf("fmt.Sprintf(\"%s/%%s%s\", id)", colPath, action) return } @@ -37,7 +37,7 @@ type templateInput struct { Name string Package string Fields []fieldInfo - PathArgs []string + PluralResourceName string CollectionFunctionArg string ResourceFunctionArg string CollectionPath string @@ -61,7 +61,7 @@ func fillTemplates() { Name: in.generatedStructure.name, Package: in.generatedStructure.pkg, Fields: in.generatedStructure.fields, - PathArgs: in.pathArgs, + PluralResourceName: in.pluralResourceName, ParentTypeName: in.parentTypeName, ExtraOptions: in.extraOptions, VersionEnabled: in.versionEnabled, @@ -70,8 +70,8 @@ func fillTemplates() { RecursiveListing: in.recursiveListing, } - if len(in.pathArgs) > 0 { - input.CollectionFunctionArg, input.ResourceFunctionArg, input.CollectionPath, input.ResourcePath = getArgsAndPaths(in.pathArgs, in.parentTypeName, "") + if len(in.pluralResourceName) > 0 { + input.CollectionFunctionArg, input.CollectionPath, input.ResourcePath = getArgsAndPaths(in.pluralResourceName, in.parentTypeName, "") } if err := structTemplate.Execute(outBuf, input); err != nil { @@ -242,9 +242,9 @@ func (c *Client) List(ctx context.Context, {{ .CollectionFunctionArg }} string, `)) var readTemplate = template.Must(template.New("").Parse(` -func (c *Client) Read(ctx context.Context, {{ .ResourceFunctionArg }} string, opt... Option) (*{{ .Name }}ReadResult, error) { - if {{ .ResourceFunctionArg }} == "" { - return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Read request") +func (c *Client) Read(ctx context.Context, id string, opt... Option) (*{{ .Name }}ReadResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Read request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -285,9 +285,9 @@ func (c *Client) Read(ctx context.Context, {{ .ResourceFunctionArg }} string, op `)) var deleteTemplate = template.Must(template.New("").Parse(` -func (c *Client) Delete(ctx context.Context, {{ .ResourceFunctionArg }} string, opt... Option) (*{{ .Name }}DeleteResult, error) { - if {{ .ResourceFunctionArg }} == "" { - return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Delete request") +func (c *Client) Delete(ctx context.Context, id string, opt... Option) (*{{ .Name }}DeleteResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Delete request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -386,9 +386,9 @@ func (c *Client) Create (ctx context.Context, {{ if .TypeOnCreate }} resourceTyp `)) var updateTemplate = template.Must(template.New("").Parse(` -func (c *Client) Update(ctx context.Context, {{ .ResourceFunctionArg }} string, version uint32, opt... Option) (*{{ .Name }}UpdateResult, error) { - if {{ .ResourceFunctionArg }} == "" { - return nil, fmt.Errorf("empty {{ .ResourceFunctionArg }} value passed into Update request") +func (c *Client) Update(ctx context.Context, id string, version uint32, opt... Option) (*{{ .Name }}UpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into Update request") } if c.client == nil { return nil, fmt.Errorf("nil client") @@ -401,7 +401,7 @@ func (c *Client) Update(ctx context.Context, {{ .ResourceFunctionArg }} string, if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into Update request and automatic versioning not specified") } - existingTarget, existingErr := c.Read(ctx, {{ .ResourceFunctionArg }}, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -465,10 +465,10 @@ var sliceSubtypeTemplate = template.Must(template.New("").Funcs( {{ range $key, $value := $input.SliceSubtypes }} {{ $fullName := print $op $key }} {{ $actionName := kebabCase $fullName }} -{{ $resPath := getPathWithAction $input.PathArgs $input.ParentTypeName $actionName }} -func (c *Client) {{ $fullName }}(ctx context.Context, {{ $input.ResourceFunctionArg }} string, version uint32, {{ $value }} []string, opt... Option) (*{{ $input.Name }}UpdateResult, error) { - if {{ $input.ResourceFunctionArg }} == "" { - return nil, fmt.Errorf("empty {{ $input.ResourceFunctionArg }} value passed into {{ $fullName }} request") +{{ $resPath := getPathWithAction $input.PluralResourceName $input.ParentTypeName $actionName }} +func (c *Client) {{ $fullName }}(ctx context.Context, id string, version uint32, {{ $value }} []string, opt... Option) (*{{ $input.Name }}UpdateResult, error) { + if id == "" { + return nil, fmt.Errorf("empty id value passed into {{ $fullName }} request") } {{ if ( not ( eq $op "Set" ) ) }}if len({{ $value }}) == 0 { return nil, errors.New("empty {{ $value }} passed into {{ $fullName }} request") @@ -484,7 +484,7 @@ func (c *Client) {{ $fullName }}(ctx context.Context, {{ $input.ResourceFunction if !opts.withAutomaticVersioning { return nil, errors.New("zero version number passed into {{ $fullName }} request") } - existingTarget, existingErr := c.Read(ctx, {{ $input.ResourceFunctionArg }}, append([]Option{WithSkipCurlOutput(true)}, opt...)...) + existingTarget, existingErr := c.Read(ctx, id, append([]Option{WithSkipCurlOutput(true)}, opt...)...) if existingErr != nil { if api.AsServerError(existingErr) != nil { return nil, fmt.Errorf("error from controller when performing initial check-and-set read: %w", existingErr) @@ -761,7 +761,7 @@ func kebabCase(in string) string { return strcase.ToKebab(in) } -func getPathWithAction(resArgs []string, parentTypeName, action string) string { - _, _, _, resPath := getArgsAndPaths(resArgs, parentTypeName, action) +func getPathWithAction(plResName, parentTypeName, action string) string { + _, _, resPath := getArgsAndPaths(plResName, parentTypeName, action) return resPath } diff --git a/internal/cmd/base/base.go b/internal/cmd/base/base.go index 7e3b8e347..0738b5ec6 100644 --- a/internal/cmd/base/base.go +++ b/internal/cmd/base/base.go @@ -71,16 +71,17 @@ type Command struct { FlagRecoveryConfig string flagOutputCurlString bool - FlagScopeId string - FlagScopeName string - FlagId string - FlagName string - FlagDescription string - FlagAuthMethodId string - FlagHostCatalogId string - FlagVersion int - FlagRecursive bool - FlagFilter string + FlagScopeId string + FlagScopeName string + FlagId string + FlagName string + FlagDescription string + FlagAuthMethodId string + FlagHostCatalogId string + FlagCredentialStoreId string + FlagVersion int + FlagRecursive bool + FlagFilter string client *api.Client } diff --git a/internal/cmd/base/format.go b/internal/cmd/base/format.go index 94192e98f..c455e14f0 100644 --- a/internal/cmd/base/format.go +++ b/internal/cmd/base/format.go @@ -139,8 +139,10 @@ func WrapMap(prefixSpaces, maxLengthOverride int, input map[string]interface{}) } // PrintApiError prints the given API error, optionally with context -// information, to the UI in the appropriate format -func (c *Command) PrintApiError(in *api.Error, contextStr string) { +// information, to the UI in the appropriate format. WithAttributeFieldPrefix is +// used, all other options are ignored. +func (c *Command) PrintApiError(in *api.Error, contextStr string, opt ...Option) { + opts := getOpts(opt...) switch Format(c.UI) { case "json": output := struct { @@ -204,8 +206,14 @@ func (c *Command) PrintApiError(in *api.Error, contextStr string) { // TODO: Report useful error messages related to "update_mask". continue } + var fNameParts []string + if opts.withAttributeFieldPrefix != "" && strings.HasPrefix(field.Name, "attributes.") { + fNameParts = append(fNameParts, opts.withAttributeFieldPrefix) + } + fNameParts = append(fNameParts, strings.ReplaceAll(strings.TrimPrefix(field.Name, "attributes."), "_", "-")) + fName := strings.Join(fNameParts, "-") output = append(output, - fmt.Sprintf(" Name: -%s", strings.ReplaceAll(strings.TrimPrefix(field.Name, "attributes."), "_", "-")), + fmt.Sprintf(" Name: -%s", fName), fmt.Sprintf(" Error: %s", field.Description), ) } diff --git a/internal/cmd/base/initial_resources.go b/internal/cmd/base/initial_resources.go index a7f1f6c84..4959586bc 100644 --- a/internal/cmd/base/initial_resources.go +++ b/internal/cmd/base/initial_resources.go @@ -516,7 +516,7 @@ func (b *Server) CreateInitialTarget(ctx context.Context) (target.Target, error) if err != nil { return nil, fmt.Errorf("error creating in memory target: %w", err) } - tt, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...) + tt, _, _, err := targetRepo.CreateTcpTarget(cancelCtx, t, opts...) if err != nil { return nil, fmt.Errorf("error saving target to the db: %w", err) } diff --git a/internal/cmd/base/option.go b/internal/cmd/base/option.go index 5fadf9220..f4f582989 100644 --- a/internal/cmd/base/option.go +++ b/internal/cmd/base/option.go @@ -24,6 +24,7 @@ type Options struct { withSkipTargetCreation bool withContainerImage string withDialect string + withAttributeFieldPrefix string } func getDefaultOptions() Options { @@ -111,3 +112,11 @@ func withDialect(dialect string) Option { o.withDialect = dialect } } + +// WithAttributeFieldPrefix tells the command what prefix +// to attach to attribute fields when they are returned as errors. +func WithAttributeFieldPrefix(p string) Option { + return func(o *Options) { + o.withAttributeFieldPrefix = p + } +} diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index 9cd8fdc75..c37dac3ff 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -8,6 +8,8 @@ import ( "github.com/hashicorp/boundary/internal/cmd/commands/authtokenscmd" "github.com/hashicorp/boundary/internal/cmd/commands/config" "github.com/hashicorp/boundary/internal/cmd/commands/connect" + "github.com/hashicorp/boundary/internal/cmd/commands/credentiallibrariescmd" + "github.com/hashicorp/boundary/internal/cmd/commands/credentialstorescmd" "github.com/hashicorp/boundary/internal/cmd/commands/database" "github.com/hashicorp/boundary/internal/cmd/commands/dev" "github.com/hashicorp/boundary/internal/cmd/commands/groupscmd" @@ -325,6 +327,102 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { }, nil }, + "credential-libraries": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "credential-libraries read": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "credential-libraries delete": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "credential-libraries list": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, + "credential-libraries create": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credential-libraries create vault": func() (cli.Command, error) { + return &credentiallibrariescmd.VaultCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credential-libraries update": func() (cli.Command, error) { + return &credentiallibrariescmd.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "credential-libraries update vault": func() (cli.Command, error) { + return &credentiallibrariescmd.VaultCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + + "credential-stores": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "credential-stores read": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "credential-stores delete": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "credential-stores list": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, + "credential-stores create": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credential-stores create vault": func() (cli.Command, error) { + return &credentialstorescmd.VaultCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credential-stores update": func() (cli.Command, error) { + return &credentialstorescmd.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "credential-stores update vault": func() (cli.Command, error) { + return &credentialstorescmd.VaultCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "groups": func() (cli.Command, error) { return &groupscmd.Command{ Command: base.NewCommand(ui), @@ -798,6 +896,24 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "set-host-sets", }, nil }, + "targets add-credential-libraries": func() (cli.Command, error) { + return &targetscmd.Command{ + Command: base.NewCommand(ui), + Func: "add-credential-libraries", + }, nil + }, + "targets remove-credential-libraries": func() (cli.Command, error) { + return &targetscmd.Command{ + Command: base.NewCommand(ui), + Func: "remove-credential-libraries", + }, nil + }, + "targets set-credential-libraries": func() (cli.Command, error) { + return &targetscmd.Command{ + Command: base.NewCommand(ui), + Func: "set-credential-libraries", + }, nil + }, "users": func() (cli.Command, error) { return &userscmd.Command{ diff --git a/internal/cmd/commands/accountscmd/accounts.gen.go b/internal/cmd/commands/accountscmd/accounts.gen.go index 52cc2e27c..c7e1a1269 100644 --- a/internal/cmd/commands/accountscmd/accounts.gen.go +++ b/internal/cmd/commands/accountscmd/accounts.gen.go @@ -226,7 +226,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go index 3c87cc52c..dbb6b95fd 100644 --- a/internal/cmd/commands/accountscmd/oidc_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/oidc_accounts.gen.go @@ -195,7 +195,9 @@ func (c *OidcCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/accountscmd/password_accounts.gen.go b/internal/cmd/commands/accountscmd/password_accounts.gen.go index 7cf27fc0d..3b71d47a9 100644 --- a/internal/cmd/commands/accountscmd/password_accounts.gen.go +++ b/internal/cmd/commands/accountscmd/password_accounts.gen.go @@ -195,7 +195,9 @@ func (c *PasswordCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/authmethodscmd/authmethods.gen.go b/internal/cmd/commands/authmethodscmd/authmethods.gen.go index bb3a5c904..bac8cbf27 100644 --- a/internal/cmd/commands/authmethodscmd/authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/authmethods.gen.go @@ -193,7 +193,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go index b962acce5..5e9a31fcb 100644 --- a/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/oidc_authmethods.gen.go @@ -210,7 +210,9 @@ func (c *OidcCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go index e242ddd3c..9c8b78e16 100644 --- a/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go +++ b/internal/cmd/commands/authmethodscmd/password_authmethods.gen.go @@ -200,7 +200,9 @@ func (c *PasswordCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/authtokenscmd/authtokens.gen.go b/internal/cmd/commands/authtokenscmd/authtokens.gen.go index 8a80bef69..09dc13629 100644 --- a/internal/cmd/commands/authtokenscmd/authtokens.gen.go +++ b/internal/cmd/commands/authtokenscmd/authtokens.gen.go @@ -184,7 +184,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go new file mode 100644 index 000000000..ed0f4f5b4 --- /dev/null +++ b/internal/cmd/commands/credentiallibrariescmd/credentiallibraries.gen.go @@ -0,0 +1,264 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentiallibrariescmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential library" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("credential library") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"credential-store-id", "filter"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[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, "credential library", flagsMap, c.Func) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "credential library" + switch c.Func { + case "list": + c.plural = "credential librarys" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentiallibraries.Option + + if strutil.StrListContains(flagsMap[c.Func], "credential-store-id") { + switch c.Func { + case "list": + if c.FlagCredentialStoreId == "" { + c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + credentiallibrariesClient := credentiallibraries.NewClient(client) + + if c.FlagFilter != "" { + opts = append(opts, credentiallibraries.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = credentiallibrariesClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + result, err = credentiallibrariesClient.Delete(c.Context, c.FlagId, opts...) + + case "list": + listResult, err = credentiallibrariesClient.List(c.Context, c.FlagCredentialStoreId, opts...) + + } + + result, err = executeExtraActions(c, result, err, credentiallibrariesClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output("The delete operation completed successfully.") + } + + return base.CommandSuccess + + case "list": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItems(listResult); !ok { + return base.CommandCliError + } + + case "table": + listedItems := listResult.GetItems().([]*credentiallibraries.CredentialLibrary) + c.UI.Output(c.printListTable(listedItems)) + } + + return base.CommandSuccess + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]credentiallibraries.Option) bool { return true } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentiallibrariescmd/funcs.go b/internal/cmd/commands/credentiallibrariescmd/funcs.go new file mode 100644 index 000000000..3e73fed7d --- /dev/null +++ b/internal/cmd/commands/credentiallibrariescmd/funcs.go @@ -0,0 +1,178 @@ +package credentiallibrariescmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary credential-libraries [sub command] [options] [args]", + "", + " This command allows operations on Boundary credential library resources. Example:", + "", + " Read a credential library:", + "", + ` $ boundary credential-libraries read -id clvlt_1234567890`, + "", + " Please see the credential-libraries subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-libraries create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary credential library resources. Example:", + "", + " Create a credential library:", + "", + ` $ boundary credential-libraries create vault -credential-store-id csvlt_1234567890 -vault-path "/some/path"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-libraries update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary credential library resources. Example:", + "", + " Update a vault-type credential library:", + "", + ` $ boundary credential-libraries update vault -id clvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + default: + helpStr = helpMap["base"]() + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*credentiallibraries.CredentialLibrary) string { + if len(items) == 0 { + return "No credential library found" + } + + var output []string + output = []string{ + "", + "Credential Library information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if m.Id != "" { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + ) + } else { + output = append(output, + fmt.Sprintf(" ID: %s", "(not available)"), + ) + } + if m.Version > 0 { + output = append(output, + fmt.Sprintf(" Version: %d", m.Version), + ) + } + if m.Type != "" { + output = append(output, + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(result api.GenericResult) string { + item := result.GetItem().(*credentiallibraries.CredentialLibrary) + nonAttributeMap := map[string]interface{}{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + if item.Version != 0 { + nonAttributeMap["Version"] = item.Version + } + if !item.CreatedTime.IsZero() { + nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123) + } + if !item.UpdatedTime.IsZero() { + nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123) + } + if item.CredentialStoreId != "" { + nonAttributeMap["Credential Store ID"] = item.CredentialStoreId + } + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + if item.Type != "" { + nonAttributeMap["Type"] = item.Type + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + + ret := []string{ + "", + "Credential Library information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + if item.Scope != nil { + ret = append(ret, + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + ) + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "path": "Path", + "http_method": "HTTP Method", + "http_request_body": "HTTP Request Body", +} diff --git a/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go b/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go new file mode 100644 index 000000000..c87725a88 --- /dev/null +++ b/internal/cmd/commands/credentiallibrariescmd/vault_credentiallibraries.gen.go @@ -0,0 +1,243 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentiallibrariescmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initVaultFlags() { + flagsOnce.Do(func() { + extraFlags := extraVaultActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsVaultMap[k] = append(flagsVaultMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*VaultCommand)(nil) + _ cli.CommandAutocomplete = (*VaultCommand)(nil) +) + +type VaultCommand struct { + *base.Command + + Func string + + plural string + + extraVaultCmdVars +} + +func (c *VaultCommand) AutocompleteArgs() complete.Predictor { + initVaultFlags() + return complete.PredictAnything +} + +func (c *VaultCommand) AutocompleteFlags() complete.Flags { + initVaultFlags() + return c.Flags().Completions() +} + +func (c *VaultCommand) Synopsis() string { + if extra := extraVaultSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential library" + + synopsisStr = fmt.Sprintf("%s %s", "vault-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *VaultCommand) Help() string { + initVaultFlags() + + var helpStr string + helpMap := common.HelpMap("credential library") + + switch c.Func { + default: + + helpStr = c.extraVaultHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsVaultMap = map[string][]string{ + + "create": {"credential-store-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *VaultCommand) Flags() *base.FlagSets { + if len(flagsVaultMap[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, "vault-type credential library", flagsVaultMap, c.Func) + + extraVaultFlagsFunc(c, set, f) + + return set +} + +func (c *VaultCommand) Run(args []string) int { + initVaultFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "vault-type credential library" + switch c.Func { + case "list": + c.plural = "vault-type credential librarys" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsVaultMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentiallibraries.Option + + if strutil.StrListContains(flagsVaultMap[c.Func], "credential-store-id") { + switch c.Func { + case "create": + if c.FlagCredentialStoreId == "" { + c.PrintCliError(errors.New("CredentialStore ID must be passed in via -credential-store-id or BOUNDARY_CREDENTIAL_STORE_ID")) + return base.CommandUserError + } + } + } + + client, err := c.Client() + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + credentiallibrariesClient := credentiallibraries.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, credentiallibraries.DefaultName()) + default: + opts = append(opts, credentiallibraries.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, credentiallibraries.DefaultDescription()) + default: + opts = append(opts, credentiallibraries.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, credentiallibraries.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, credentiallibraries.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ok := extraVaultFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = credentiallibrariesClient.Create(c.Context, c.FlagCredentialStoreId, opts...) + + case "update": + result, err = credentiallibrariesClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraVaultActions(c, result, err, credentiallibrariesClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + opts = append(opts, base.WithAttributeFieldPrefix("vault")) + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomVaultActionOutput(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(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + extraVaultActionsFlagsMapFunc = func() map[string][]string { return nil } + extraVaultSynopsisFunc = func(*VaultCommand) string { return "" } + extraVaultFlagsFunc = func(*VaultCommand, *base.FlagSets, *base.FlagSet) {} + extraVaultFlagsHandlingFunc = func(*VaultCommand, *base.FlagSets, *[]credentiallibraries.Option) bool { return true } + executeExtraVaultActions = func(_ *VaultCommand, inResult api.GenericResult, inErr error, _ *credentiallibraries.Client, _ uint32, _ []credentiallibraries.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomVaultActionOutput = func(*VaultCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentiallibrariescmd/vault_funcs.go b/internal/cmd/commands/credentiallibrariescmd/vault_funcs.go new file mode 100644 index 000000000..a3bfedeeb --- /dev/null +++ b/internal/cmd/commands/credentiallibrariescmd/vault_funcs.go @@ -0,0 +1,117 @@ +package credentiallibrariescmd + +import ( + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/shared-secure-libs/configutil" +) + +func init() { + extraVaultFlagsFunc = extraVaultFlagsFuncImpl + extraVaultActionsFlagsMapFunc = extraVaultActionsFlagsMapFuncImpl + extraVaultFlagsHandlingFunc = extraVaultFlagHandlingFuncImpl +} + +const ( + pathFlagName = "vault-path" + httpMethodFlagName = "vault-http-method" + httpRequestBodyFlagName = "vault-http-request-body" +) + +type extraVaultCmdVars struct { + flagPath string + flagHttpMethod string + flagHttpRequestBody string +} + +func extraVaultActionsFlagsMapFuncImpl() map[string][]string { + flags := map[string][]string{ + "create": { + pathFlagName, + httpMethodFlagName, + httpRequestBodyFlagName, + }, + } + flags["update"] = flags["create"] + return flags +} + +func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSet) { + f := set.NewFlagSet("Vault Credential Library Options") + + for _, name := range flagsVaultMap[c.Func] { + switch name { + case pathFlagName: + f.StringVar(&base.StringVar{ + Name: pathFlagName, + Target: &c.flagPath, + Usage: "The path in vault to request credentials from.", + }) + case httpMethodFlagName: + f.StringVar(&base.StringVar{ + Name: httpMethodFlagName, + Target: &c.flagHttpMethod, + Usage: "The http method the library should use when communicating with vault.", + }) + case httpRequestBodyFlagName: + f.StringVar(&base.StringVar{ + Name: httpRequestBodyFlagName, + Target: &c.flagHttpRequestBody, + Usage: "The http request body the library uses to communicate with vault. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", + }) + } + } +} + +func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]credentiallibraries.Option) bool { + switch c.flagPath { + case "": + default: + *opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryPath(c.flagPath)) + } + switch c.flagHttpMethod { + case "": + case "null": + *opts = append(*opts, credentiallibraries.DefaultVaultCredentialLibraryHttpMethod()) + default: + *opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpMethod(c.flagHttpMethod)) + } + switch c.flagHttpRequestBody { + case "": + case "null": + *opts = append(*opts, credentiallibraries.DefaultVaultCredentialLibraryHttpRequestBody()) + default: + rb, _ := configutil.ParsePath(c.flagHttpRequestBody) + *opts = append(*opts, credentiallibraries.WithVaultCredentialLibraryHttpRequestBody(rb)) + } + + return true +} + +func (c *VaultCommand) extraVaultHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-libraries create vault -credential-store-id [options] [args]", + "", + " Create a vault-type credential library. Example:", + "", + ` $ boundary credential-libraries create vault -credential-store-id csvlt_1234567890 -vault-path "/some/path"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-libraries update vault [options] [args]", + "", + " Update a vault-type credential library given its ID. Example:", + "", + ` $ boundary credential-libraries update vault -id clvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go new file mode 100644 index 000000000..f21f91c1f --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/credentialstores.gen.go @@ -0,0 +1,269 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentialstorescmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initFlags() { + flagsOnce.Do(func() { + extraFlags := extraActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsMap[k] = append(flagsMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*Command)(nil) + _ cli.CommandAutocomplete = (*Command)(nil) +) + +type Command struct { + *base.Command + + Func string + + plural string +} + +func (c *Command) AutocompleteArgs() complete.Predictor { + initFlags() + return complete.PredictAnything +} + +func (c *Command) AutocompleteFlags() complete.Flags { + initFlags() + return c.Flags().Completions() +} + +func (c *Command) Synopsis() string { + if extra := extraSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential store" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("credential store") + + switch c.Func { + + case "read": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "delete": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + case "list": + helpStr = helpMap[c.Func]() + c.Flags().Help() + + default: + + helpStr = c.extraHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsMap = map[string][]string{ + + "read": {"id"}, + + "delete": {"id"}, + + "list": {"scope-id", "filter", "recursive"}, +} + +func (c *Command) Flags() *base.FlagSets { + if len(flagsMap[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, "credential store", flagsMap, c.Func) + + extraFlagsFunc(c, set, f) + + return set +} + +func (c *Command) Run(args []string) int { + initFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + case "create", "update": + return cli.RunResultHelp + + } + + c.plural = "credential store" + switch c.Func { + case "list": + c.plural = "credential stores" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentialstores.Option + + if strutil.StrListContains(flagsMap[c.Func], "scope-id") { + switch c.Func { + case "list": + 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 err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + credentialstoresClient := credentialstores.NewClient(client) + + switch c.FlagRecursive { + case true: + opts = append(opts, credentialstores.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, credentialstores.WithFilter(c.FlagFilter)) + } + + var version uint32 + + if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + var listResult api.GenericListResult + + switch c.Func { + + case "read": + result, err = credentialstoresClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + result, err = credentialstoresClient.Delete(c.Context, c.FlagId, opts...) + + case "list": + listResult, err = credentialstoresClient.List(c.Context, c.FlagScopeId, opts...) + + } + + result, err = executeExtraActions(c, result, err, credentialstoresClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomActionOutput(c) + if err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + if output { + return base.CommandSuccess + } + + switch c.Func { + + case "delete": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + + case "table": + c.UI.Output("The delete operation completed successfully.") + } + + return base.CommandSuccess + + case "list": + switch base.Format(c.UI) { + case "json": + if ok := c.PrintJsonItems(listResult); !ok { + return base.CommandCliError + } + + case "table": + listedItems := listResult.GetItems().([]*credentialstores.CredentialStore) + c.UI.Output(c.printListTable(listedItems)) + } + + return base.CommandSuccess + + } + + switch base.Format(c.UI) { + case "table": + c.UI.Output(printItemTable(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + flagsOnce = new(sync.Once) + + extraActionsFlagsMapFunc = func() map[string][]string { return nil } + extraSynopsisFunc = func(*Command) string { return "" } + extraFlagsFunc = func(*Command, *base.FlagSets, *base.FlagSet) {} + extraFlagsHandlingFunc = func(*Command, *base.FlagSets, *[]credentialstores.Option) bool { return true } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *credentialstores.Client, _ uint32, _ []credentialstores.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialstorescmd/funcs.go b/internal/cmd/commands/credentialstorescmd/funcs.go new file mode 100644 index 000000000..94772e536 --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/funcs.go @@ -0,0 +1,203 @@ +package credentialstorescmd + +import ( + "fmt" + "sort" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "": + return base.WrapForHelpText([]string{ + "Usage: boundary credential-stores [sub command] [options] [args]", + "", + " This command allows operations on Boundary credential store resources. Example:", + "", + " Read a credential store:", + "", + ` $ boundary credential-stores read -id csvlt_1234567890`, + "", + " Please see the credential-stores subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary credential store resources. Example:", + "", + " Create a vault-type credential store:", + "", + ` $ boundary credential-stores create vault -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary credential store resources. Example:", + "", + " Update a vault-type credential store:", + "", + ` $ boundary credential-stores update vault -id csvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + default: + helpStr = helpMap["base"]() + } + return helpStr + c.Flags().Help() +} + +func (c *Command) printListTable(items []*credentialstores.CredentialStore) string { + if len(items) == 0 { + return "No credential store found" + } + + var output []string + output = []string{ + "", + "Credential Store information:", + } + for i, m := range items { + if i > 0 { + output = append(output, "") + } + if m.Id != "" { + output = append(output, + fmt.Sprintf(" ID: %s", m.Id), + ) + } else { + output = append(output, + fmt.Sprintf(" ID: %s", "(not available)"), + ) + } + if c.FlagRecursive && m.ScopeId != "" { + output = append(output, + fmt.Sprintf(" Scope ID: %s", m.ScopeId), + ) + } + if m.Version > 0 { + output = append(output, + fmt.Sprintf(" Version: %d", m.Version), + ) + } + if m.Type != "" { + output = append(output, + fmt.Sprintf(" Type: %s", m.Type), + ) + } + if m.Name != "" { + output = append(output, + fmt.Sprintf(" Name: %s", m.Name), + ) + } + if m.Description != "" { + output = append(output, + fmt.Sprintf(" Description: %s", m.Description), + ) + } + if len(m.AuthorizedActions) > 0 { + output = append(output, + " Authorized Actions:", + base.WrapSlice(6, m.AuthorizedActions), + ) + } + } + + return base.WrapForHelpText(output) +} + +func printItemTable(result api.GenericResult) string { + item := result.GetItem().(*credentialstores.CredentialStore) + nonAttributeMap := map[string]interface{}{} + if item.Id != "" { + nonAttributeMap["ID"] = item.Id + } + if item.Version != 0 { + nonAttributeMap["Version"] = item.Version + } + if !item.CreatedTime.IsZero() { + nonAttributeMap["Created Time"] = item.CreatedTime.Local().Format(time.RFC1123) + } + if !item.UpdatedTime.IsZero() { + nonAttributeMap["Updated Time"] = item.UpdatedTime.Local().Format(time.RFC1123) + } + if item.Name != "" { + nonAttributeMap["Name"] = item.Name + } + if item.Description != "" { + nonAttributeMap["Description"] = item.Description + } + if item.Type != "" { + nonAttributeMap["Type"] = item.Type + } + + maxLength := base.MaxAttributesLength(nonAttributeMap, item.Attributes, keySubstMap) + + ret := []string{ + "", + "Credential Store information:", + base.WrapMap(2, maxLength+2, nonAttributeMap), + } + + if item.Scope != nil { + ret = append(ret, + "", + " Scope:", + base.ScopeInfoForOutput(item.Scope, maxLength), + ) + } + + if len(item.AuthorizedActions) > 0 { + ret = append(ret, + "", + " Authorized Actions:", + base.WrapSlice(4, item.AuthorizedActions), + ) + } + + if len(item.AuthorizedCollectionActions) > 0 { + keys := make([]string, 0, len(item.AuthorizedCollectionActions)) + for k := range item.AuthorizedCollectionActions { + keys = append(keys, k) + } + sort.Strings(keys) + ret = append(ret, "", + " Authorized Actions on Credential Store's Collections:", + ) + for _, key := range keys { + ret = append(ret, + fmt.Sprintf(" %s:", key), + base.WrapSlice(6, item.AuthorizedCollectionActions[key]), + ) + } + } + + if len(item.Attributes) > 0 { + ret = append(ret, + "", + " Attributes:", + base.WrapMap(4, maxLength, item.Attributes), + ) + } + + return base.WrapForHelpText(ret) +} + +var keySubstMap = map[string]string{ + "address": "Address", + "namespace": "Namespace", + "ca_cert": "CA Cert", + "tls_server_name": "TLS Server Name", + "tls_skip_verify": "Skip TLS Verification", + "token_hmac": "Token HMAC", + "client_certificate": "Client Certificate", + "client_certificate_key_hmac": "Client Certificate Key HMAC", +} diff --git a/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go new file mode 100644 index 000000000..07d7bd1d2 --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/vault_credentialstores.gen.go @@ -0,0 +1,248 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentialstorescmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/boundary/sdk/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initVaultFlags() { + flagsOnce.Do(func() { + extraFlags := extraVaultActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsVaultMap[k] = append(flagsVaultMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*VaultCommand)(nil) + _ cli.CommandAutocomplete = (*VaultCommand)(nil) +) + +type VaultCommand struct { + *base.Command + + Func string + + plural string + + extraVaultCmdVars +} + +func (c *VaultCommand) AutocompleteArgs() complete.Predictor { + initVaultFlags() + return complete.PredictAnything +} + +func (c *VaultCommand) AutocompleteFlags() complete.Flags { + initVaultFlags() + return c.Flags().Completions() +} + +func (c *VaultCommand) Synopsis() string { + if extra := extraVaultSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential store" + + synopsisStr = fmt.Sprintf("%s %s", "vault-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *VaultCommand) Help() string { + initVaultFlags() + + var helpStr string + helpMap := common.HelpMap("credential store") + + switch c.Func { + default: + + helpStr = c.extraVaultHelpFunc(helpMap) + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsVaultMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *VaultCommand) Flags() *base.FlagSets { + if len(flagsVaultMap[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, "vault-type credential store", flagsVaultMap, c.Func) + + extraVaultFlagsFunc(c, set, f) + + return set +} + +func (c *VaultCommand) Run(args []string) int { + initVaultFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + } + + c.plural = "vault-type credential store" + switch c.Func { + case "list": + c.plural = "vault-type credential stores" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsVaultMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentialstores.Option + + if strutil.StrListContains(flagsVaultMap[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 err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %s", err.Error())) + return base.CommandCliError + } + credentialstoresClient := credentialstores.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, credentialstores.DefaultName()) + default: + opts = append(opts, credentialstores.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, credentialstores.DefaultDescription()) + default: + opts = append(opts, credentialstores.WithDescription(c.FlagDescription)) + } + + switch c.FlagRecursive { + case true: + opts = append(opts, credentialstores.WithRecursive(true)) + } + + if c.FlagFilter != "" { + opts = append(opts, credentialstores.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, credentialstores.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } + + if ok := extraVaultFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = credentialstoresClient.Create(c.Context, "vault", c.FlagScopeId, opts...) + + case "update": + result, err = credentialstoresClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraVaultActions(c, result, err, credentialstoresClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + opts = append(opts, base.WithAttributeFieldPrefix("vault")) + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) + return base.CommandApiError + } + c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) + return base.CommandCliError + } + + output, err := printCustomVaultActionOutput(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(result)) + + case "json": + if ok := c.PrintJsonItem(result); !ok { + return base.CommandCliError + } + } + + return base.CommandSuccess +} + +var ( + extraVaultActionsFlagsMapFunc = func() map[string][]string { return nil } + extraVaultSynopsisFunc = func(*VaultCommand) string { return "" } + extraVaultFlagsFunc = func(*VaultCommand, *base.FlagSets, *base.FlagSet) {} + extraVaultFlagsHandlingFunc = func(*VaultCommand, *base.FlagSets, *[]credentialstores.Option) bool { return true } + executeExtraVaultActions = func(_ *VaultCommand, inResult api.GenericResult, inErr error, _ *credentialstores.Client, _ uint32, _ []credentialstores.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomVaultActionOutput = func(*VaultCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialstorescmd/vault_funcs.go b/internal/cmd/commands/credentialstorescmd/vault_funcs.go new file mode 100644 index 000000000..61f796c56 --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/vault_funcs.go @@ -0,0 +1,186 @@ +package credentialstorescmd + +import ( + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/shared-secure-libs/configutil" +) + +func init() { + extraVaultFlagsFunc = extraVaultFlagsFuncImpl + extraVaultActionsFlagsMapFunc = extraVaultActionsFlagsMapFuncImpl + extraVaultFlagsHandlingFunc = extraVaultFlagHandlingFuncImpl +} + +const ( + addressFlagName = "vault-address" + namespaceFlagName = "vault-namespace" + vaultCaCertFlagName = "vault-ca-cert" + tlsServerNameFlagName = "vault-tls-server-name" + tlsSkipVerifyFlagName = "vault-tls-skip-verify" + vaultTokenFlagName = "vault-token" + clientCertificateFlagName = "vault-client-certificate" + clientCertificateKeyFlagName = "vault-client-certificate-key" +) + +type extraVaultCmdVars struct { + flagAddress string + flagNamespace string + flagCaCert string + flagVaultToken string + flagClientCert string + flagClientCertKey string + flagTlsServerName string + flagTlsSkipVerify bool +} + +func extraVaultActionsFlagsMapFuncImpl() map[string][]string { + flags := map[string][]string{ + "create": { + addressFlagName, + namespaceFlagName, + vaultCaCertFlagName, + tlsServerNameFlagName, + tlsSkipVerifyFlagName, + vaultTokenFlagName, + clientCertificateFlagName, + clientCertificateKeyFlagName, + }, + } + flags["update"] = flags["create"] + return flags +} + +func extraVaultFlagsFuncImpl(c *VaultCommand, set *base.FlagSets, _ *base.FlagSet) { + f := set.NewFlagSet("Vault Credential Store Options") + + for _, name := range flagsVaultMap[c.Func] { + switch name { + case addressFlagName: + f.StringVar(&base.StringVar{ + Name: addressFlagName, + Target: &c.flagAddress, + Usage: "The address of the Vault server. This should be a complete URL such as https://127.0.0.1:8200", + }) + case namespaceFlagName: + f.StringVar(&base.StringVar{ + Name: namespaceFlagName, + Target: &c.flagNamespace, + Usage: "The vault namespace the store should use.", + }) + case vaultCaCertFlagName: + f.StringVar(&base.StringVar{ + Name: vaultCaCertFlagName, + Target: &c.flagCaCert, + Usage: "The CA Cert to use when connecting to vault. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", + }) + case tlsServerNameFlagName: + f.StringVar(&base.StringVar{ + Name: tlsServerNameFlagName, + Target: &c.flagTlsServerName, + Usage: `Name to use as the SNI host when connecting via TLS.`, + }) + case tlsSkipVerifyFlagName: + f.BoolVar(&base.BoolVar{ + Name: tlsSkipVerifyFlagName, + Target: &c.flagTlsSkipVerify, + Usage: "Whether to skip tls verification.", + }) + case vaultTokenFlagName: + f.StringVar(&base.StringVar{ + Name: vaultTokenFlagName, + Target: &c.flagVaultToken, + Usage: "The vault token to use when boundary connects to vault for this store.", + }) + case clientCertificateFlagName: + f.StringVar(&base.StringVar{ + Name: clientCertificateFlagName, + Target: &c.flagClientCert, + Usage: "The client certificate to use when boundary connects to vault for this store. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.", + }) + case clientCertificateKeyFlagName: + f.StringVar(&base.StringVar{ + Name: clientCertificateKeyFlagName, + Target: &c.flagClientCertKey, + Usage: `The client certificate's private key to use when boundary connects to vault for this store. This can be the value itself, refer to a file on disk (file://) from which the value will be read, or an env var (env://) from which the value will be read.`, + }) + } + } +} + +func extraVaultFlagHandlingFuncImpl(c *VaultCommand, f *base.FlagSets, opts *[]credentialstores.Option) bool { + switch c.flagAddress { + case "": + default: + *opts = append(*opts, credentialstores.WithVaultCredentialStoreAddress(c.flagAddress)) + } + switch c.flagNamespace { + case "": + case "null": + *opts = append(*opts, credentialstores.DefaultVaultCredentialStoreNamespace()) + default: + *opts = append(*opts, credentialstores.WithVaultCredentialStoreNamespace(c.flagNamespace)) + } + switch c.flagVaultToken { + case "": + default: + *opts = append(*opts, credentialstores.WithVaultCredentialStoreToken(c.flagVaultToken)) + } + switch c.flagCaCert { + case "": + case "null": + *opts = append(*opts, credentialstores.DefaultVaultCredentialStoreCaCert()) + default: + cer, _ := configutil.ParsePath(c.flagCaCert) + *opts = append(*opts, credentialstores.WithVaultCredentialStoreCaCert(cer)) + } + switch c.flagClientCert { + case "": + case "null": + *opts = append(*opts, credentialstores.DefaultVaultCredentialStoreClientCertificate()) + default: + cer, _ := configutil.ParsePath(c.flagClientCert) + *opts = append(*opts, credentialstores.WithVaultCredentialStoreClientCertificate(cer)) + } + switch c.flagClientCertKey { + case "": + case "null": + *opts = append(*opts, credentialstores.DefaultVaultCredentialStoreClientCertificateKey()) + default: + cer, _ := configutil.ParsePath(c.flagClientCert) + *opts = append(*opts, credentialstores.WithVaultCredentialStoreClientCertificateKey(cer)) + } + if c.flagTlsSkipVerify { + *opts = append(*opts, credentialstores.WithVaultCredentialStoreTlsSkipVerify(c.flagTlsSkipVerify)) + } + + return true +} + +func (c *VaultCommand) extraVaultHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores create vault [options] [args]", + "", + " Create a vault-type credential store. Example:", + "", + ` $ boundary credential-stores create vault -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores update vault [options] [args]", + "", + " Update a vault-type credential store given its ID. Example:", + "", + ` $ boundary credential-stores update vault -id csvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/groupscmd/groups.gen.go b/internal/cmd/commands/groupscmd/groups.gen.go index 8bffb3d9d..30ef3804e 100644 --- a/internal/cmd/commands/groupscmd/groups.gen.go +++ b/internal/cmd/commands/groupscmd/groups.gen.go @@ -267,7 +267,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go index e7692f6dc..4f2911507 100644 --- a/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/hostcatalogs.gen.go @@ -193,7 +193,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go index f4fb8970c..77a81a3e0 100644 --- a/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go +++ b/internal/cmd/commands/hostcatalogscmd/static_hostcatalogs.gen.go @@ -198,7 +198,9 @@ func (c *StaticCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostscmd/hosts.gen.go b/internal/cmd/commands/hostscmd/hosts.gen.go index e31d1c365..0035a0aee 100644 --- a/internal/cmd/commands/hostscmd/hosts.gen.go +++ b/internal/cmd/commands/hostscmd/hosts.gen.go @@ -204,7 +204,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostscmd/static_hosts.gen.go b/internal/cmd/commands/hostscmd/static_hosts.gen.go index 32dc0c26f..f5d1dfc35 100644 --- a/internal/cmd/commands/hostscmd/static_hosts.gen.go +++ b/internal/cmd/commands/hostscmd/static_hosts.gen.go @@ -195,7 +195,9 @@ func (c *StaticCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostsetscmd/hostsets.gen.go b/internal/cmd/commands/hostsetscmd/hostsets.gen.go index e66b8b405..71fc1b615 100644 --- a/internal/cmd/commands/hostsetscmd/hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/hostsets.gen.go @@ -234,7 +234,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go index 08b1ddec1..b55e097e4 100644 --- a/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go +++ b/internal/cmd/commands/hostsetscmd/static_hostsets.gen.go @@ -193,7 +193,9 @@ func (c *StaticCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go index 89f50aeae..20e4febd6 100644 --- a/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go +++ b/internal/cmd/commands/managedgroupscmd/managedgroups.gen.go @@ -204,7 +204,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go index 3d9144489..9f0f61f54 100644 --- a/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go +++ b/internal/cmd/commands/managedgroupscmd/oidc_managedgroups.gen.go @@ -195,7 +195,9 @@ func (c *OidcCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/rolescmd/roles.gen.go b/internal/cmd/commands/rolescmd/roles.gen.go index 1db46b122..3fac0b168 100644 --- a/internal/cmd/commands/rolescmd/roles.gen.go +++ b/internal/cmd/commands/rolescmd/roles.gen.go @@ -291,7 +291,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/scopescmd/scopes.gen.go b/internal/cmd/commands/scopescmd/scopes.gen.go index 5fab8d26d..53572047e 100644 --- a/internal/cmd/commands/scopescmd/scopes.gen.go +++ b/internal/cmd/commands/scopescmd/scopes.gen.go @@ -241,7 +241,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/sessionscmd/sessions.gen.go b/internal/cmd/commands/sessionscmd/sessions.gen.go index 78fca6392..26a07bab1 100644 --- a/internal/cmd/commands/sessionscmd/sessions.gen.go +++ b/internal/cmd/commands/sessionscmd/sessions.gen.go @@ -191,7 +191,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/targetscmd/funcs.go b/internal/cmd/commands/targetscmd/funcs.go index ac69499ea..f8ab1cb18 100644 --- a/internal/cmd/commands/targetscmd/funcs.go +++ b/internal/cmd/commands/targetscmd/funcs.go @@ -1,6 +1,9 @@ package targetscmd import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" "strings" "time" @@ -25,17 +28,21 @@ func init() { } type extraCmdVars struct { - flagHostSets []string - flagHostId string - sar *targets.SessionAuthorizationResult + flagHostSets []string + flagCredentialLibraries []string + flagHostId string + sar *targets.SessionAuthorizationResult } func extraActionsFlagsMapFuncImpl() map[string][]string { return map[string][]string{ - "authorize-session": {"id", "host-id"}, - "add-host-sets": {"id", "host-set", "version"}, - "remove-host-sets": {"id", "host-set", "version"}, - "set-host-sets": {"id", "host-set", "version"}, + "authorize-session": {"id", "host-id"}, + "add-host-sets": {"id", "host-set", "version"}, + "remove-host-sets": {"id", "host-set", "version"}, + "set-host-sets": {"id", "host-set", "version"}, + "add-credential-libraries": {"id", "credential-library", "version"}, + "remove-credential-libraries": {"id", "credential-library", "version"}, + "set-credential-libraries": {"id", "credential-library", "version"}, } } @@ -53,6 +60,18 @@ func extraSynopsisFuncImpl(c *Command) string { } return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth) + case "add-credential-libraries", "set-credential-libraries", "remove-credential-libraries": + var in string + switch { + case strings.HasPrefix(c.Func, "add"): + in = "Add credential library to" + case strings.HasPrefix(c.Func, "set"): + in = "Set the full contents of the credential libraries on" + case strings.HasPrefix(c.Func, "remove"): + in = "Remove credential libraries from" + } + return wordwrap.WrapString(fmt.Sprintf("%s a target", in), base.TermWidth) + case "authorize-session": return "Request session authorization against the target" @@ -136,6 +155,42 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { "", "", }) + case "add-credential-libraries": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target add-credential-libraries [options] [args]", + "", + " This command allows adding credential-library resources to target resources. Example:", + "", + " Add credential-library resources to a tcp-type target:", + "", + ` $ boundary targets add-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`, + "", + "", + }) + case "remove-credential-libraries": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target remove-credential-libraries [options] [args]", + "", + " This command allows removing credential-library resources from target resources. Example:", + "", + " Remove credential-library resources from a tcp-type target:", + "", + ` $ boundary targets remove-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890 -credential-library clvlt_0987654321`, + "", + "", + }) + case "set-credential-libraries": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary target set-credential-libraries [options] [args]", + "", + " This command allows setting the complete set of credential-library resources on a target resource. Example:", + "", + " Set credential-library resources on a tcp-type target:", + "", + ` $ boundary targets set-credential-libraries -id ttcp_1234567890 -credential-library clvlt_1234567890`, + "", + "", + }) case "authorize-session": helpStr = base.WrapForHelpText([]string{ "Usage: boundary target authorize-session [options] [args]", @@ -171,6 +226,12 @@ func extraFlagsFuncImpl(c *Command, _ *base.FlagSets, f *base.FlagSet) { Target: &c.flagHostId, Usage: "The ID of a specific host to connect to out of the hosts from the target's host sets. If not specified, one is chosen at random.", }) + case "credential-library": + f.StringSliceVar(&base.StringSliceVar{ + Name: "credential-library", + Target: &c.flagCredentialLibraries, + Usage: "The credential-library resource to add, remove, or set. May be specified multiple times.", + }) } } @@ -252,6 +313,23 @@ func extraFlagsHandlingFuncImpl(c *Command, _ *base.FlagSets, opts *[]targets.Op } } + case "add-credential-libraries", "remove-credential-libraries": + if len(c.flagCredentialLibraries) == 0 { + c.UI.Error("No credential-library supplied via -credential-library") + return false + } + + case "set-credential-libraries": + switch len(c.flagCredentialLibraries) { + case 0: + c.UI.Error("No credential-libraries supplied via -credential-library") + return false + case 1: + if c.flagCredentialLibraries[0] == "null" { + c.flagCredentialLibraries = nil + } + } + case "authorize-session": if len(c.flagHostId) != 0 { *opts = append(*opts, targets.WithHostId(c.flagHostId)) @@ -269,6 +347,12 @@ func executeExtraActionsImpl(c *Command, origResult api.GenericResult, origError return targetClient.RemoveHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...) case "set-host-sets": return targetClient.SetHostSets(c.Context, c.FlagId, version, c.flagHostSets, opts...) + case "add-credential-libraries": + return targetClient.AddCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...) + case "remove-credential-libraries": + return targetClient.RemoveCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...) + case "set-credential-libraries": + return targetClient.SetCredentialLibraries(c.Context, c.FlagId, version, c.flagCredentialLibraries, opts...) case "authorize-session": var err error c.plural = "a session against target" @@ -388,6 +472,20 @@ func printItemTable(result api.GenericResult) string { } } + var libraryMaps []map[string]interface{} + if len(item.CredentialLibraries) > 0 { + for _, lib := range item.CredentialLibraries { + m := map[string]interface{}{ + "ID": lib.Id, + "Credential Store ID": lib.CredentialStoreId, + } + libraryMaps = append(libraryMaps, m) + } + if l := len("Credential Store ID"); l > maxLength { + maxLength = l + } + } + ret := []string{ "", "Target information:", @@ -426,6 +524,18 @@ func printItemTable(result api.GenericResult) string { } } + if len(item.CredentialLibraries) > 0 { + ret = append(ret, + " Credential Libraries:", + ) + for _, m := range libraryMaps { + ret = append(ret, + base.WrapMap(4, maxLength, m), + "", + ) + } + } + if len(item.Attributes) > 0 { ret = append(ret, " Attributes:", @@ -466,9 +576,63 @@ func printCustomActionOutputImpl(c *Command) (bool, error) { ret = append(ret, "", "Target information:") ret = append(ret, - // We do +2 because there is another +2 offset for host sets below + // We do +2 because there is another +2 offset for credentials below base.WrapMap(2, maxLength+2, nonAttributeMap), ) + + ret = append(ret, + "", + ) + if len(item.Credentials) > 0 { + ret = append(ret, + " Credentials:", + ) + + for _, cred := range item.Credentials { + ret = append(ret, + fmt.Sprintf(" Credential Store ID: %s", cred.CredentialLibrary.CredentialStoreId), + fmt.Sprintf(" Credential Library ID: %s", cred.CredentialLibrary.Id), + fmt.Sprintf(" Credential Library Type: %s", cred.CredentialLibrary.Type)) + + if len(cred.CredentialLibrary.Name) > 0 { + ret = append(ret, + fmt.Sprintf(" Credential Library Name: %s", cred.CredentialLibrary.Name)) + } + if len(cred.CredentialLibrary.Description) > 0 { + ret = append(ret, + fmt.Sprintf(" Credential Library Description: %s", cred.CredentialLibrary.Description)) + } + var secretStr string + switch cred.CredentialLibrary.Type { + case "vault": + // If it's Vault, the result will be JSON, except in + // specific circumstances that aren't used for + // credential fetching. So we can take the bytes + // as-is (after base64-decoding), but we'll format + // it nicely. + in, err := base64.StdEncoding.DecodeString(cred.Secret) + if err != nil { + return false, fmt.Errorf("Error decoding secret as base64: %w", err) + } + dst := new(bytes.Buffer) + if err := json.Indent(dst, in, "", " "); err != nil { + return false, fmt.Errorf("Error pretty-printing JSON: %w", err) + } + secretStr = dst.String() + default: + // If it's not Vault, and not another known type, + // print out the base64-encoded value and leave it + // to the user to sort out. + secretStr = fmt.Sprintf(" %s", secretStr) + } + ret = append(ret, + fmt.Sprintf(" Secret:"), + fmt.Sprintf(" %s", secretStr), + "", + ) + } + } + c.UI.Output(base.WrapForHelpText(ret)) return true, nil @@ -511,6 +675,17 @@ func exampleOutput() string { HostCatalogId: "hcst_1234567890", }, }, + CredentialLibraryIds: []string{"clvlt_1234567890", "clvlt_0987654321"}, + CredentialLibraries: []*targets.CredentialLibrary{ + { + Id: "clvlt_1234567890", + CredentialStoreId: "csvlt_1234567890", + }, + { + Id: "clvlt_098765421", + CredentialStoreId: "clvlt_0987654321", + }, + }, Attributes: map[string]interface{}{ "default_port": 22, }, diff --git a/internal/cmd/commands/targetscmd/targets.gen.go b/internal/cmd/commands/targetscmd/targets.gen.go index 97e4a14f5..63f2ebe22 100644 --- a/internal/cmd/commands/targetscmd/targets.gen.go +++ b/internal/cmd/commands/targetscmd/targets.gen.go @@ -213,6 +213,30 @@ func (c *Command) Run(args []string) int { version = uint32(c.FlagVersion) } + case "add-credential-libraries": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "remove-credential-libraries": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + case "set-credential-libraries": + switch c.FlagVersion { + case 0: + opts = append(opts, targets.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + } if ok := extraFlagsHandlingFunc(c, f, &opts); !ok { @@ -240,7 +264,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/targetscmd/tcp_targets.gen.go b/internal/cmd/commands/targetscmd/tcp_targets.gen.go index fda35bbe8..7c644cbe3 100644 --- a/internal/cmd/commands/targetscmd/tcp_targets.gen.go +++ b/internal/cmd/commands/targetscmd/tcp_targets.gen.go @@ -200,7 +200,9 @@ func (c *TcpCommand) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/commands/userscmd/users.gen.go b/internal/cmd/commands/userscmd/users.gen.go index 7650d389f..d57f4faf2 100644 --- a/internal/cmd/commands/userscmd/users.gen.go +++ b/internal/cmd/commands/userscmd/users.gen.go @@ -267,7 +267,9 @@ func (c *Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/cmd/common/flags.go b/internal/cmd/common/flags.go index d2977f74d..5ab75fc46 100644 --- a/internal/cmd/common/flags.go +++ b/internal/cmd/common/flags.go @@ -65,6 +65,13 @@ func PopulateCommonFlags(c *base.Command, f *base.FlagSet, resourceType string, Target: &c.FlagHostCatalogId, Usage: "The host-catalog resource to use for the operation.", }) + case "credential-store-id": + f.StringVar(&base.StringVar{ + Name: "credential-store-id", + EnvVar: "BOUNDARY_CREDENTIAL_STORE_ID", + Target: &c.FlagCredentialStoreId, + Usage: "The credential-store resource to use for the operation.", + }) case "recursive": f.BoolVar(&base.BoolVar{ Name: "recursive", diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 9b902d53e..66406631a 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -62,6 +62,12 @@ type cmdInfo struct { // NeedsSubtypeInCreate controls whether the sub-type must be passed in as // an argument to a create call. Targets need this, accounts do not, etc. NeedsSubtypeInCreate bool + + // PrefixAttributeFieldErrorsWithSubactionPrefix will prepend the value in + // SubActionPrefix when reporting errors which are reported in flag format. + // This allows the flags to be defined differently from the the attribute + // names in the API. + PrefixAttributeFieldErrorsWithSubactionPrefix bool } var inputStructs = map[string][]*cmdInfo{ @@ -157,6 +163,59 @@ var inputStructs = map[string][]*cmdInfo{ Container: "Scope", }, }, + "credentialstores": { + { + ResourceType: resource.CredentialStore.String(), + Pkg: "credentialstores", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + HasExtraHelpFunc: true, + Container: "Scope", + HasId: true, + }, + { + ResourceType: resource.CredentialStore.String(), + Pkg: "credentialstores", + StdActions: []string{"create", "update"}, + SubActionPrefix: "vault", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + PrefixAttributeFieldErrorsWithSubactionPrefix: true, + }, + }, + "credentiallibraries": { + { + ResourceType: resource.CredentialLibrary.String(), + Pkg: "credentiallibraries", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + HasExtraHelpFunc: true, + Container: "CredentialStore", + HasId: true, + }, + { + ResourceType: resource.CredentialLibrary.String(), + Pkg: "credentiallibraries", + StdActions: []string{"create", "update"}, + SubActionPrefix: "vault", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "CredentialStore", + VersionedActions: []string{"update"}, + PrefixAttributeFieldErrorsWithSubactionPrefix: true, + }, + }, "groups": { { ResourceType: resource.Group.String(), @@ -327,7 +386,7 @@ var inputStructs = map[string][]*cmdInfo{ HasName: true, HasDescription: true, Container: "Scope", - VersionedActions: []string{"add-host-sets", "remove-host-sets", "set-host-sets"}, + VersionedActions: []string{"add-host-sets", "remove-host-sets", "set-host-sets", "add-credential-libraries", "remove-credential-libraries", "set-credential-libraries"}, }, { ResourceType: resource.Target.String(), diff --git a/internal/cmd/gencli/templates.go b/internal/cmd/gencli/templates.go index 63a7ef8ce..7bbbcc44c 100644 --- a/internal/cmd/gencli/templates.go +++ b/internal/cmd/gencli/templates.go @@ -361,7 +361,11 @@ func (c *{{ camelCase .SubActionPrefix }}Command) Run(args []string) int { if err != nil { if apiErr := api.AsServerError(err); apiErr != nil { - c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural)) + var opts []base.Option + {{ if (and $input.SubActionPrefix $input.PrefixAttributeFieldErrorsWithSubactionPrefix ) }} + opts = append(opts, base.WithAttributeFieldPrefix("{{ $input.SubActionPrefix }}")) + {{ end }} + c.PrintApiError(apiErr, fmt.Sprintf("Error from controller when performing %s on %s", c.Func, c.plural), opts...) return base.CommandApiError } c.PrintCliError(fmt.Errorf("Error trying to %s %s: %s", c.Func, c.plural, err.Error())) diff --git a/internal/credential/credential.go b/internal/credential/credential.go new file mode 100644 index 000000000..4f64aca2d --- /dev/null +++ b/internal/credential/credential.go @@ -0,0 +1,111 @@ +// Package credential defines interfaces shared by other packages that +// manage credentials for Boundary sessions. +package credential + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db/timestamp" +) + +// An Entity is an object distinguished by its identity, rather than its +// attributes. It can contain value objects and other entities. +type Entity interface { + GetPublicId() string +} + +// An Aggregate is an entity that is the root of a transactional +// consistency boundary. +type Aggregate interface { + Entity + GetVersion() uint32 + GetCreateTime() *timestamp.Timestamp + GetUpdateTime() *timestamp.Timestamp +} + +// A Resource is an aggregate with a name and description. +type Resource interface { + Aggregate + GetName() string + GetDescription() string +} + +// A Store is a resource that can store, retrieve, and potentially generate +// credentials of differing types and access levels. It belongs to a scope +// and must support the principle of least privilege by providing +// mechanisms to limit the credentials it can access to the minimum +// necessary for the scope it is in. +type Store interface { + Resource + GetScopeId() string +} + +// A Library is a resource that provides credentials that are of the same +// type and access level from a single store. +type Library interface { + Resource + GetStoreId() string +} + +// Purpose is the purpose of the credential. +type Purpose string + +// Credential purpose values. +const ( + // ApplicationPurpose is a credential used for application specific + // purposes. Application credentials are returned to the user. + ApplicationPurpose Purpose = "application" + + // IngressPurpose is a credential used by a boundary worker to secure + // the connection between the user and the worker. Ingress credentials + // are never returned to the user. + IngressPurpose Purpose = "ingress" + + // EgressPurpose is a credential used by a boundary worker to secure + // the connection between the worker and the endpoint. Egress + // credentials are never returned to the user. + EgressPurpose Purpose = "egress" +) + +// SecretData represents secret data. +type SecretData interface{} + +// Credential is an entity containing secret data. +type Credential interface { + Entity + Secret() SecretData +} + +// Dynamic is a credential generated by a library for a specific session. +type Dynamic interface { + Credential + GetSessionId() string + Library() Library + Purpose() Purpose +} + +// A Request represents a request for a credential from the SourceId for +// the given purpose. For dynamic credentials, the SourceId is the PublicId +// of a credential library. +type Request struct { + SourceId string + Purpose Purpose +} + +// Issuer issues dynamic credentials. +type Issuer interface { + // Issue issues dynamic credentials for a session from the requested + // libraries and for the requested purposes. The sessionId must be a + // valid sessionId. The SourceId in each request must be the public id + // of a library the Issuer can issue credentials from. + // + // If Issue encounters an error, it returns no credentials and revokes + // any credentials issued before encountering the error. + Issue(ctx context.Context, sessionId string, requests []Request) ([]Dynamic, error) +} + +// Revoker revokes dynamic credentials. +type Revoker interface { + // Revoke revokes the dynamic credentials issued for sessionid. + Revoke(ctx context.Context, sessionId string) error +} diff --git a/internal/credential/store/credential.pb.go b/internal/credential/store/credential.pb.go new file mode 100644 index 000000000..c73878c95 --- /dev/null +++ b/internal/credential/store/credential.pb.go @@ -0,0 +1,459 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/storage/credential/store/v1/credential.proto + +// Package store provides protobufs for storing types in the credential package. + +package store + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Store struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,2,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"` +} + +func (x *Store) Reset() { + *x = Store{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Store) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Store) ProtoMessage() {} + +func (x *Store) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[0] + 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 Store.ProtoReflect.Descriptor instead. +func (*Store) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{0} +} + +func (x *Store) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Store) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +type Library struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The store_id of the owning store and must be set. + // @inject_tag: `gorm:"not_null"` + StoreId string `protobuf:"bytes,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"` +} + +func (x *Library) Reset() { + *x = Library{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Library) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Library) ProtoMessage() {} + +func (x *Library) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[1] + 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 Library.ProtoReflect.Descriptor instead. +func (*Library) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{1} +} + +func (x *Library) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Library) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +type Credential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` +} + +func (x *Credential) Reset() { + *x = Credential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Credential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Credential) ProtoMessage() {} + +func (x *Credential) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_store_v1_credential_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 Credential.ProtoReflect.Descriptor instead. +func (*Credential) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{2} +} + +func (x *Credential) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +type Static struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The store_id of the owning store and must be set. + // @inject_tag: `gorm:"not_null"` + StoreId string `protobuf:"bytes,2,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"` +} + +func (x *Static) Reset() { + *x = Static{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Static) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Static) ProtoMessage() {} + +func (x *Static) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[3] + 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 Static.ProtoReflect.Descriptor instead. +func (*Static) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{3} +} + +func (x *Static) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Static) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +type Dynamic struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The library_id of the owning library and must be set. + // @inject_tag: `gorm:"not_null"` + LibraryId string `protobuf:"bytes,2,opt,name=library_id,json=libraryId,proto3" json:"library_id,omitempty" gorm:"not_null"` +} + +func (x *Dynamic) Reset() { + *x = Dynamic{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Dynamic) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Dynamic) ProtoMessage() {} + +func (x *Dynamic) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_store_v1_credential_proto_msgTypes[4] + 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 Dynamic.ProtoReflect.Descriptor instead. +func (*Dynamic) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP(), []int{4} +} + +func (x *Dynamic) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Dynamic) GetLibraryId() string { + if x != nil { + return x.LibraryId + } + return "" +} + +var File_controller_storage_credential_store_v1_credential_proto protoreflect.FileDescriptor + +var file_controller_storage_credential_store_v1_credential_proto_rawDesc = []byte{ + 0x0a, 0x37, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x26, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, + 0x31, 0x22, 0x3f, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x49, 0x64, 0x22, 0x41, 0x0a, 0x07, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x49, 0x64, 0x22, 0x29, 0x0a, 0x0a, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, + 0x22, 0x40, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x49, 0x64, 0x22, 0x45, 0x0a, 0x07, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x12, 0x1b, 0x0a, + 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x49, 0x64, 0x42, 0x3f, 0x5a, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_controller_storage_credential_store_v1_credential_proto_rawDescOnce sync.Once + file_controller_storage_credential_store_v1_credential_proto_rawDescData = file_controller_storage_credential_store_v1_credential_proto_rawDesc +) + +func file_controller_storage_credential_store_v1_credential_proto_rawDescGZIP() []byte { + file_controller_storage_credential_store_v1_credential_proto_rawDescOnce.Do(func() { + file_controller_storage_credential_store_v1_credential_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_credential_store_v1_credential_proto_rawDescData) + }) + return file_controller_storage_credential_store_v1_credential_proto_rawDescData +} + +var file_controller_storage_credential_store_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_controller_storage_credential_store_v1_credential_proto_goTypes = []interface{}{ + (*Store)(nil), // 0: controller.storage.credential.store.v1.Store + (*Library)(nil), // 1: controller.storage.credential.store.v1.Library + (*Credential)(nil), // 2: controller.storage.credential.store.v1.Credential + (*Static)(nil), // 3: controller.storage.credential.store.v1.Static + (*Dynamic)(nil), // 4: controller.storage.credential.store.v1.Dynamic +} +var file_controller_storage_credential_store_v1_credential_proto_depIdxs = []int32{ + 0, // [0:0] is the sub-list for method output_type + 0, // [0:0] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_controller_storage_credential_store_v1_credential_proto_init() } +func file_controller_storage_credential_store_v1_credential_proto_init() { + if File_controller_storage_credential_store_v1_credential_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_credential_store_v1_credential_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Store); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_store_v1_credential_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Library); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_store_v1_credential_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Credential); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_store_v1_credential_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Static); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_store_v1_credential_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Dynamic); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_credential_store_v1_credential_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_credential_store_v1_credential_proto_goTypes, + DependencyIndexes: file_controller_storage_credential_store_v1_credential_proto_depIdxs, + MessageInfos: file_controller_storage_credential_store_v1_credential_proto_msgTypes, + }.Build() + File_controller_storage_credential_store_v1_credential_proto = out.File + file_controller_storage_credential_store_v1_credential_proto_rawDesc = nil + file_controller_storage_credential_store_v1_credential_proto_goTypes = nil + file_controller_storage_credential_store_v1_credential_proto_depIdxs = nil +} diff --git a/internal/credential/subtype.go b/internal/credential/subtype.go new file mode 100644 index 000000000..2533f636b --- /dev/null +++ b/internal/credential/subtype.go @@ -0,0 +1,69 @@ +package credential + +import ( + "strings" + "sync" +) + +type Subtype int + +const ( + UnknownSubtype Subtype = iota + VaultSubtype +) + +func (t Subtype) String() string { + switch t { + case VaultSubtype: + return "vault" + } + return "unknown" +} + +// Subtype uses the provided subtype +func SubtypeFromType(t string) Subtype { + switch { + case strings.EqualFold(strings.TrimSpace(t), VaultSubtype.String()): + return VaultSubtype + } + return UnknownSubtype +} + +func SubtypeFromId(id string) Subtype { + i := strings.Index(id, "_") + if i == -1 { + return UnknownSubtype + } + prefix := id[:i] + + subtypeMu.RLock() + subtype, ok := subtypes[prefix] + subtypeMu.RUnlock() + if !ok { + return UnknownSubtype + } + return subtype +} + +var ( + subtypeMu sync.RWMutex + subtypes = make(map[string]Subtype) +) + +// Register registers the prefixes for a Subtype. Register panics if the +// subtype is unknown. +func Register(subtype Subtype, prefixes ...string) { + subtypeMu.Lock() + defer subtypeMu.Unlock() + + switch subtype { + case VaultSubtype: + default: + panic("credential.Register: subtype is unknown ") + } + + for _, prefix := range prefixes { + prefix = strings.TrimSpace(prefix) + subtypes[prefix] = subtype + } +} diff --git a/internal/credential/subtype_test.go b/internal/credential/subtype_test.go new file mode 100644 index 000000000..2d2fb23a8 --- /dev/null +++ b/internal/credential/subtype_test.go @@ -0,0 +1,35 @@ +package credential_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/stretchr/testify/assert" +) + +func TestSubtypeFromId(t *testing.T) { + credential.Register(credential.VaultSubtype, "csvlt") + tests := []struct { + name string + given string + want credential.Subtype + }{ + {"empty-string", "", credential.UnknownSubtype}, + {"no-prefix-delimiter", "csvlt1234", credential.UnknownSubtype}, + {"prefix-first", "_csvlt_1234", credential.UnknownSubtype}, + {"unknown-prefix", "kaz_1234", credential.UnknownSubtype}, + {"prefix-no-id", "csvlt_", credential.VaultSubtype}, + {"vault-prefix", "csvlt_1234", credential.VaultSubtype}, + {"prefix-no-delimiter-no-id", "csvlt", credential.UnknownSubtype}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := credential.SubtypeFromId(tt.given) + assert.Equalf(t, tt.want, got, "given: %s", tt.given) + if got != tt.want { + t.Errorf("(%s): expected %s, actual %s", tt.given, tt.want, got) + } + }) + } +} diff --git a/internal/credential/vault/client_certificate.go b/internal/credential/vault/client_certificate.go new file mode 100644 index 000000000..7a4852111 --- /dev/null +++ b/internal/credential/vault/client_certificate.go @@ -0,0 +1,151 @@ +package vault + +import ( + "context" + "crypto/ed25519" + "crypto/hmac" + "crypto/sha256" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" + "google.golang.org/protobuf/proto" +) + +// ClientCertificate contains a client certificate and a private key for +// the certificate. It is owned by a credential store. +type ClientCertificate struct { + *store.ClientCertificate + tableName string `gorm:"-"` +} + +// NewClientCertificate creates a new in memory ClientCertificate. +func NewClientCertificate(certificate []byte, key KeySecret) (*ClientCertificate, error) { + const op = "vault.NewClientCertificate" + if len(certificate) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no certificate") + } + + certificateCopy := make([]byte, len(certificate)) + copy(certificateCopy, certificate) + + var keyCopy KeySecret + if len(key) > 0 { + keyCopy = make(KeySecret, len(key)) + copy(keyCopy, key) + } + + c := &ClientCertificate{ + ClientCertificate: &store.ClientCertificate{ + Certificate: certificateCopy, + CertificateKey: keyCopy, + }, + } + return c, nil +} + +func allocClientCertificate() *ClientCertificate { + return &ClientCertificate{ + ClientCertificate: &store.ClientCertificate{}, + } +} + +func (c *ClientCertificate) clone() *ClientCertificate { + cp := proto.Clone(c.ClientCertificate) + return &ClientCertificate{ + ClientCertificate: cp.(*store.ClientCertificate), + } +} + +// TableName returns the table name. +func (c *ClientCertificate) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "credential_vault_client_certificate" +} + +// SetTableName sets the table name. +func (c *ClientCertificate) SetTableName(n string) { + c.tableName = n +} + +func (c *ClientCertificate) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(ClientCertificate).encrypt" + if len(c.CertificateKey) == 0 { + errors.New(errors.InvalidParameter, op, "no certificate key defined") + } + if err := structwrapping.WrapStruct(ctx, cipher, c.ClientCertificate, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Encrypt)) + } + c.KeyId = cipher.KeyID() + if err := c.hmacCertificateKey(ctx, cipher); err != nil { + errors.Wrap(err, op) + } + return nil +} + +func (c *ClientCertificate) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(ClientCertificate).decrypt" + if err := structwrapping.UnwrapStruct(ctx, cipher, c.ClientCertificate, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt)) + } + return nil +} + +func (c *ClientCertificate) hmacCertificateKey(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(ClientCertificate).hmacCertificateKey" + if cipher == nil { + return errors.New(errors.InvalidParameter, op, "missing cipher") + } + reader, err := kms.NewDerivedReader(cipher, 32, []byte(c.StoreId), nil) + if err != nil { + return errors.Wrap(err, op) + } + key, _, err := ed25519.GenerateKey(reader) + if err != nil { + return errors.New(errors.Encrypt, op, "unable to generate derived key") + } + mac := hmac.New(sha256.New, key) + _, _ = mac.Write(c.CertificateKey) + c.CertificateKeyHmac = mac.Sum(nil) + return nil +} + +func (c *ClientCertificate) insertQuery() (query string, queryValues []interface{}) { + query = upsertClientCertQuery + queryValues = []interface{}{ + c.StoreId, + c.Certificate, + c.CtCertificateKey, + c.CertificateKeyHmac, + c.KeyId, + } + return +} + +func (c *ClientCertificate) deleteQuery() (query string, queryValues []interface{}) { + query = deleteClientCertQuery + queryValues = []interface{}{ + c.StoreId, + } + return +} + +func (c *ClientCertificate) oplogMessage(opType db.OpType) *oplog.Message { + msg := oplog.Message{ + Message: c.clone(), + TypeName: c.TableName(), + } + switch opType { + case db.CreateOp, db.UpdateOp: + msg.OpType = oplog.OpType_OP_TYPE_CREATE + case db.DeleteOp: + msg.OpType = oplog.OpType_OP_TYPE_DELETE + } + return &msg +} diff --git a/internal/credential/vault/client_certificate_test.go b/internal/credential/vault/client_certificate_test.go new file mode 100644 index 000000000..21af5dd26 --- /dev/null +++ b/internal/credential/vault/client_certificate_test.go @@ -0,0 +1,125 @@ +package vault + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// certPen and keyPem are copied from crypto/tls/example_test.go in the Go +// standard library. +const ( + certPem = `-----BEGIN CERTIFICATE----- +MIIBhTCCASugAwIBAgIQIRi6zePL6mKjOipn+dNuaTAKBggqhkjOPQQDAjASMRAw +DgYDVQQKEwdBY21lIENvMB4XDTE3MTAyMDE5NDMwNloXDTE4MTAyMDE5NDMwNlow +EjEQMA4GA1UEChMHQWNtZSBDbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABD0d +7VNhbWvZLWPuj/RtHFjvtJBEwOkhbN/BnnE8rnZR8+sbwnc/KhCk3FhnpHZnQz7B +5aETbbIgmuvewdjvSBSjYzBhMA4GA1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggr +BgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MCkGA1UdEQQiMCCCDmxvY2FsaG9zdDo1 +NDUzgg4xMjcuMC4wLjE6NTQ1MzAKBggqhkjOPQQDAgNIADBFAiEA2zpJEPQyz6/l +Wf86aX6PepsntZv2GYlA5UpabfT2EZICICpJ5h/iI+i341gBmLiAFQOyTDT+/wQc +6MF9+Yw1Yy0t +-----END CERTIFICATE-----` + + keyPem = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIrYSSNQFaA2Hwf1duRSxKtLYX5CB04fSeQ6tF1aY/PuoAoGCCqGSM49 +AwEHoUQDQgAEPR3tU2Fta9ktY+6P9G0cWO+0kETA6SFs38GecTyudlHz6xvCdz8q +EKTcWGekdmdDPsHloRNtsiCa697B2O9IFA== +-----END EC PRIVATE KEY-----` +) + +func TestClientCertificate_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + + kkms := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + type args struct { + certificate []byte + key []byte + } + + tests := []struct { + name string + args args + want *ClientCertificate + wantErr bool + wantEncryptErr bool + }{ + { + name: "missing-certificate", + args: args{ + key: []byte(keyPem), + }, + want: nil, + wantErr: true, + }, + { + name: "valid-missing-key", + args: args{ + certificate: []byte(certPem), + }, + want: &ClientCertificate{ + ClientCertificate: &store.ClientCertificate{ + Certificate: []byte(certPem), + }, + }, + wantEncryptErr: true, + }, + { + name: "valid", + args: args{ + certificate: []byte(certPem), + key: []byte(keyPem), + }, + want: &ClientCertificate{ + ClientCertificate: &store.ClientCertificate{ + Certificate: []byte(certPem), + CertificateKey: []byte(keyPem), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + + databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) + require.NoError(err) + require.NotNil(databaseWrapper) + + got, err := NewClientCertificate(tt.args.certificate, tt.args.key) + if tt.wantErr { + assert.Error(err) + require.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + + want := tt.want + assert.Empty(got.CtCertificateKey) + assert.Equal(want, got) + + err = got.encrypt(ctx, databaseWrapper) + if tt.wantEncryptErr { + require.Error(err) + } else { + require.NoError(err) + require.NoError(got.decrypt(ctx, databaseWrapper)) + } + }) + } +} diff --git a/internal/credential/vault/credential.go b/internal/credential/vault/credential.go new file mode 100644 index 000000000..9b06f65c1 --- /dev/null +++ b/internal/credential/vault/credential.go @@ -0,0 +1,168 @@ +package vault + +import ( + "time" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A CredentialStatus represents the status of a vault credential. +type CredentialStatus string + +const ( + // ActiveCredential represents a vault credential that is being used in + // an active session. Credentials in this state are renewed before they + // expire. + ActiveCredential CredentialStatus = "active" + + // RevokeCredential represents a vault credential that needs to be + // revoked. + RevokeCredential CredentialStatus = "revoke" + + // RevokedCredential represents a credential that has been revoked. This is a + // terminal status. It does not transition to ExpiredCredential. + RevokedCredential CredentialStatus = "revoked" + + // ExpiredCredential represents a credential that expired. This is a terminal + // status. It does not transition to RevokedCredential. + ExpiredCredential CredentialStatus = "expired" +) + +const ( + externalIdSentinel = "\ufffenone" + + // UnknownCredentialStatus represents a credential that has an unknown + // status. + UnknownCredentialStatus CredentialStatus = "unknown" +) + +// A Credential contains the data for a Vault lease. It is owned by a credential library. +type Credential struct { + *store.Credential + tableName string `gorm:"-"` + expiration time.Duration `gorm:"-"` +} + +func newCredential(libraryId, sessionId, externalId string, tokenHmac []byte, expiration time.Duration) (*Credential, error) { + const op = "vault.newCredential" + if libraryId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no library id") + } + if sessionId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no session id") + } + if len(tokenHmac) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no tokenHmac") + } + + status := string(ActiveCredential) + if externalId == "" { + externalId = externalIdSentinel + status = string(UnknownCredentialStatus) + } + + l := &Credential{ + expiration: expiration.Round(time.Second), + Credential: &store.Credential{ + LibraryId: libraryId, + SessionId: sessionId, + ExternalId: externalId, + TokenHmac: tokenHmac, + Status: status, + }, + } + return l, nil +} + +func allocCredential() *Credential { + return &Credential{ + Credential: &store.Credential{}, + } +} + +func (c *Credential) clone() *Credential { + cp := proto.Clone(c.Credential) + return &Credential{ + expiration: c.expiration, + Credential: cp.(*store.Credential), + } +} + +// TableName returns the table name. +func (c *Credential) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "credential_vault_credential" +} + +// SetTableName sets the table name. +func (c *Credential) SetTableName(n string) { + c.tableName = n +} + +func (c *Credential) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{c.PublicId}, + "resource-type": []string{"credential-vault-credential"}, + "op-type": []string{op.String()}, + } + if c.LibraryId != "" { + metadata["library-id"] = []string{c.LibraryId} + } + return metadata +} + +func (c *Credential) insertQuery() (query string, queryValues []interface{}) { + queryValues = []interface{}{ + c.PublicId, + c.LibraryId, + c.SessionId, + c.TokenHmac, + c.ExternalId, + c.IsRenewable, + c.Status, + "now()", + } + switch { + case c.expiration == 0: + query = insertCredentialWithInfiniteExpirationQuery + default: + query = insertCredentialWithExpirationQuery + queryValues = append(queryValues, int(c.expiration.Round(time.Second).Seconds())) + } + return +} + +func (c *Credential) updateSessionQuery(purpose credential.Purpose) (query string, queryValues []interface{}) { + queryValues = []interface{}{ + c.PublicId, + c.LibraryId, + c.SessionId, + string(purpose), + } + query = updateSessionCredentialQuery + return +} + +func (c *Credential) updateExpirationQuery() (query string, queryValues []interface{}) { + queryValues = []interface{}{ + int(c.expiration.Round(time.Second).Seconds()), + c.PublicId, + } + query = updateCredentialExpirationQuery + return +} + +func (c *Credential) updateStatusQuery(status CredentialStatus) (query string, queryValues []interface{}) { + queryValues = []interface{}{ + status, + c.PublicId, + } + query = updateCredentialStatusQuery + return +} diff --git a/internal/credential/vault/credential_library.go b/internal/credential/vault/credential_library.go new file mode 100644 index 000000000..6de708262 --- /dev/null +++ b/internal/credential/vault/credential_library.go @@ -0,0 +1,86 @@ +package vault + +import ( + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A Method represents an HTTP method used for communicating with Vault. +type Method string + +// HTTP methods use for communicating with Vault. +const ( + MethodGet Method = "GET" + MethodPost Method = "POST" +) + +// A CredentialLibrary contains a Vault path and is owned by a credential +// store. +type CredentialLibrary struct { + *store.CredentialLibrary + tableName string `gorm:"-"` +} + +// NewCredentialLibrary creates a new in memory CredentialLibrary +// for a Vault backend at vaultPath assigned to storeId. +// Name, description, method, and request body are the only valid options. +// All other options are ignored. +func NewCredentialLibrary(storeId string, vaultPath string, opt ...Option) (*CredentialLibrary, error) { + const op = "vault.NewCredentialLibrary" + opts := getOpts(opt...) + + l := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: storeId, + Name: opts.withName, + Description: opts.withDescription, + VaultPath: vaultPath, + HttpRequestBody: opts.withRequestBody, + HttpMethod: string(opts.withMethod), + }, + } + + return l, nil +} + +func allocCredentialLibrary() *CredentialLibrary { + return &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{}, + } +} + +func (l *CredentialLibrary) clone() *CredentialLibrary { + cp := proto.Clone(l.CredentialLibrary) + return &CredentialLibrary{ + CredentialLibrary: cp.(*store.CredentialLibrary), + } +} + +// TableName returns the table name. +func (l *CredentialLibrary) TableName() string { + if l.tableName != "" { + return l.tableName + } + return "credential_vault_library" +} + +// SetTableName sets the table name. +func (l *CredentialLibrary) SetTableName(n string) { + l.tableName = n +} + +func (l *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{l.PublicId}, + "resource-type": []string{"credential-vault-library"}, + "op-type": []string{op.String()}, + } + if l.StoreId != "" { + metadata["store-id"] = []string{l.StoreId} + } + return metadata +} + +var _ credential.Library = (*CredentialLibrary)(nil) diff --git a/internal/credential/vault/credential_library_test.go b/internal/credential/vault/credential_library_test.go new file mode 100644 index 000000000..5752d6bbb --- /dev/null +++ b/internal/credential/vault/credential_library_test.go @@ -0,0 +1,226 @@ +package vault + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMethodType(t *testing.T) { + t.Parallel() + assert := assert.New(t) + assert.IsType(MethodGet, MethodPost) + assert.IsType(MethodPost, MethodGet) +} + +func TestCredentialLibrary_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.PublicId, 1)[0] + + type args struct { + storeId string + vaultPath string + opts []Option + } + + tests := []struct { + name string + args args + want *CredentialLibrary + wantErr bool + wantCreateErr bool + }{ + { + name: "missing-store-id", + args: args{ + storeId: "", + vaultPath: "vault/path", + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + VaultPath: "vault/path", + }, + }, + wantCreateErr: true, + }, + { + name: "missing-vault-path", + args: args{ + storeId: cs.PublicId, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + }, + }, + wantCreateErr: true, + }, + { + name: "no-options", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + VaultPath: "vault/path", + }, + }, + wantCreateErr: true, + }, + { + name: "with-name", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithName("test-name"), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + VaultPath: "vault/path", + Name: "test-name", + }, + }, + wantCreateErr: true, + }, + { + name: "with-name-method", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithMethod(MethodGet), + WithName("test-name"), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + VaultPath: "vault/path", + Name: "test-name", + HttpMethod: string(MethodGet), + }, + }, + }, + { + name: "valid-with-description", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + VaultPath: "vault/path", + Description: "test-description", + }, + }, + wantCreateErr: true, + }, + { + name: "valid-with-post-method", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithMethod(MethodPost), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + HttpMethod: "POST", + VaultPath: "vault/path", + }, + }, + }, + { + name: "valid-with-post-method-and-body", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithMethod(MethodPost), + WithRequestBody([]byte("body")), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + HttpMethod: "POST", + VaultPath: "vault/path", + HttpRequestBody: []byte("body"), + }, + }, + }, + { + name: "get-method-with-body", + args: args{ + storeId: cs.PublicId, + vaultPath: "vault/path", + opts: []Option{ + WithMethod(MethodGet), + WithRequestBody([]byte("body")), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.PublicId, + VaultPath: "vault/path", + HttpRequestBody: []byte("body"), + HttpMethod: "GET", + }, + }, + wantCreateErr: true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + got, err := NewCredentialLibrary(tt.args.storeId, tt.args.vaultPath, tt.args.opts...) + if tt.wantErr { + assert.Error(err) + require.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + + assert.Emptyf(got.PublicId, "PublicId set") + assert.Equal(tt.want, got) + + id, err := newCredentialLibraryId() + assert.NoError(err) + + tt.want.PublicId = id + got.PublicId = id + + err2 := rw.Create(ctx, got) + if tt.wantCreateErr { + assert.Error(err2) + } else { + assert.NoError(err2) + } + }) + } +} diff --git a/internal/credential/vault/credential_store.go b/internal/credential/vault/credential_store.go new file mode 100644 index 000000000..34d21a9c5 --- /dev/null +++ b/internal/credential/vault/credential_store.go @@ -0,0 +1,196 @@ +package vault + +import ( + "strings" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A CredentialStore contains credential libraries. It is owned by a scope. +type CredentialStore struct { + *store.CredentialStore + tableName string `gorm:"-"` + + clientCert *ClientCertificate `gorm:"-"` + inputToken TokenSecret `gorm:"-"` + outputToken *Token `gorm:"-"` + + privateClientCert *ClientCertificate `gorm:"-"` + privateToken *Token `gorm:"-"` +} + +// NewCredentialStore creates a new in memory CredentialStore for a Vault +// server at vaultAddress assigned to scopeId. Name, description, CA cert, +// client cert, namespace, TLS server name, and TLS skip verify are the +// only valid options. All other options are ignored. +func NewCredentialStore(scopeId string, vaultAddress string, token TokenSecret, opt ...Option) (*CredentialStore, error) { + opts := getOpts(opt...) + cs := &CredentialStore{ + inputToken: token, + clientCert: opts.withClientCert, + CredentialStore: &store.CredentialStore{ + ScopeId: scopeId, + Name: opts.withName, + Description: opts.withDescription, + VaultAddress: vaultAddress, + CaCert: opts.withCACert, + Namespace: opts.withNamespace, + TlsServerName: opts.withTlsServerName, + TlsSkipVerify: opts.withTlsSkipVerify, + }, + } + return cs, nil +} + +func allocCredentialStore() *CredentialStore { + return &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + } +} + +func (cs *CredentialStore) clone() *CredentialStore { + tokenCopy := make(TokenSecret, len(cs.inputToken)) + copy(tokenCopy, cs.inputToken) + var clientCertCopy *ClientCertificate + if cs.clientCert != nil { + clientCertCopy = cs.clientCert.clone() + } + cp := proto.Clone(cs.CredentialStore) + return &CredentialStore{ + inputToken: tokenCopy, + clientCert: clientCertCopy, + CredentialStore: cp.(*store.CredentialStore), + } +} + +// applyUpdate returns a new CredentialStore with the new values applied to +// this based on the passed in fieldMaskPaths. +func (cs *CredentialStore) applyUpdate(new *CredentialStore, fieldMaskPaths []string) *CredentialStore { + cp := cs.clone() + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold(nameField, f): + cp.Name = new.Name + case strings.EqualFold(descriptionField, f): + cp.Description = new.Description + case strings.EqualFold(certificateField, f): + if new.clientCert == nil { + cp.clientCert = nil + continue + } + if cp.clientCert == nil { + cp.clientCert = allocClientCertificate() + } + cp.clientCert.Certificate = new.clientCert.GetCertificate() + cp.clientCert.StoreId = cs.GetPublicId() + case strings.EqualFold(certificateKeyField, f): + if new.clientCert == nil { + cp.clientCert = nil + continue + } + if cp.clientCert == nil { + cp.clientCert = allocClientCertificate() + } + cp.clientCert.CertificateKey = new.clientCert.GetCertificateKey() + cp.clientCert.StoreId = cs.GetPublicId() + case strings.EqualFold(vaultAddressField, f): + cp.VaultAddress = new.VaultAddress + case strings.EqualFold(namespaceField, f): + cp.Namespace = new.Namespace + case strings.EqualFold(caCertField, f): + cp.CaCert = new.CaCert + case strings.EqualFold(tlsServerNameField, f): + cp.TlsServerName = new.TlsServerName + case strings.EqualFold(tlsSkipVerifyField, f): + cp.TlsSkipVerify = new.TlsSkipVerify + case strings.EqualFold(tokenField, f): + cp.inputToken = new.inputToken + } + } + return cp +} + +// TableName returns the table name. +func (cs *CredentialStore) TableName() string { + if cs.tableName != "" { + return cs.tableName + } + return "credential_vault_store" +} + +// SetTableName sets the table name. +func (cs *CredentialStore) SetTableName(n string) { + cs.tableName = n +} + +func (cs *CredentialStore) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{cs.PublicId}, + "resource-type": []string{"credential-vault-store"}, + "op-type": []string{op.String()}, + } + if cs.ScopeId != "" { + metadata["scope-id"] = []string{cs.ScopeId} + } + return metadata +} + +func (cs *CredentialStore) oplogMessage(opType db.OpType) *oplog.Message { + msg := oplog.Message{ + Message: cs.clone(), + TypeName: cs.TableName(), + } + switch opType { + case db.CreateOp: + msg.OpType = oplog.OpType_OP_TYPE_CREATE + case db.UpdateOp: + msg.OpType = oplog.OpType_OP_TYPE_UPDATE + case db.DeleteOp: + msg.OpType = oplog.OpType_OP_TYPE_DELETE + } + return &msg +} + +// Token returns the current vault token if available. +func (cs *CredentialStore) Token() *Token { + return cs.outputToken +} + +// ClientCertificate returns the client certificate if available. +func (cs *CredentialStore) ClientCertificate() *ClientCertificate { + return cs.clientCert +} + +func (cs *CredentialStore) client() (*client, error) { + const op = "vault.(CredentialStore).client" + clientConfig := &clientConfig{ + Addr: cs.VaultAddress, + Token: cs.inputToken, + CaCert: cs.CaCert, + TlsServerName: cs.TlsServerName, + TlsSkipVerify: cs.TlsSkipVerify, + Namespace: cs.Namespace, + } + if cs.clientCert != nil { + clientConfig.ClientCert = cs.clientCert.GetCertificate() + clientConfig.ClientKey = cs.clientCert.GetCertificateKey() + } + + c, err := newClient(clientConfig) + if err != nil { + return nil, errors.Wrap(err, op) + } + return c, nil +} + +func (cs *CredentialStore) softDeleteQuery() (query string, queryValues []interface{}) { + query = softDeleteStoreQuery + queryValues = []interface{}{ + cs.PublicId, + } + return +} diff --git a/internal/credential/vault/credential_store_test.go b/internal/credential/vault/credential_store_test.go new file mode 100644 index 000000000..926bed7f6 --- /dev/null +++ b/internal/credential/vault/credential_store_test.go @@ -0,0 +1,268 @@ +package vault + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCredentialStore_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + scope := prj + + inCert := testClientCert(t, testCaCert(t)) + clientCert, err := NewClientCertificate(inCert.Cert.Cert, inCert.Cert.Key) + require.NoError(t, err) + require.NotNil(t, clientCert) + + type args struct { + scopeId string + vaultAddress string + token []byte + opts []Option + } + + tests := []struct { + name string + args args + want *CredentialStore + wantErr bool + wantCreateErr bool + }{ + { + name: "missing-scope-id", + args: args{ + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + VaultAddress: "https://vault.consul.service", + }, + }, + wantCreateErr: true, + }, + { + name: "missing-vault-address", + args: args{ + scopeId: scope.PublicId, + token: []byte("token"), + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + }, + }, + wantCreateErr: true, + }, + { + name: "missing-vault-token", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + }, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + }, + }, + // The DB does not require there be at least 1 token for a store. + wantCreateErr: false, + }, + { + name: "valid-no-options", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + }, + }, + }, + { + name: "valid-with-name", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithName("test-name"), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + Name: "test-name", + }, + }, + }, + { + name: "valid-with-description", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + Description: "test-description", + }, + }, + }, + { + name: "valid-with-ca-cert", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithCACert([]byte("ca-cert")), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + CaCert: []byte("ca-cert"), + }, + }, + }, + { + name: "valid-with-namespace", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithNamespace("kazmierczak"), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + Namespace: "kazmierczak", + }, + }, + }, + { + name: "valid-with-tls-server-name", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithTlsServerName("crews"), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + TlsServerName: "crews", + }, + }, + }, + { + name: "valid-with-tls-skip-verify", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithTlsSkipVerify(true), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + TlsSkipVerify: true, + }, + }, + }, + { + name: "valid-with-client-cert", + args: args{ + scopeId: scope.PublicId, + vaultAddress: "https://vault.consul.service", + token: []byte("token"), + opts: []Option{ + WithClientCert(clientCert), + }, + }, + want: &CredentialStore{ + inputToken: []byte("token"), + clientCert: clientCert, + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + VaultAddress: "https://vault.consul.service", + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewCredentialStore(tt.args.scopeId, tt.args.vaultAddress, tt.args.token, tt.args.opts...) + if tt.wantErr { + assert.Error(err) + require.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + + assert.Emptyf(got.PublicId, "PublicId set") + assert.Equal(tt.want, got) + assert.Empty(cmp.Diff(tt.want, got.clone(), protocmp.Transform())) + + id, err := newCredentialStoreId() + assert.NoError(err) + + tt.want.PublicId = id + got.PublicId = id + + err2 := rw.Create(context.Background(), got) + if tt.wantCreateErr { + assert.Error(err2) + } else { + assert.NoError(err2) + } + }) + } +} diff --git a/internal/credential/vault/credential_test.go b/internal/credential/vault/credential_test.go new file mode 100644 index 000000000..d7519b639 --- /dev/null +++ b/internal/credential/vault/credential_test.go @@ -0,0 +1,186 @@ +package vault + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/iam" + temp "github.com/hashicorp/boundary/internal/session" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestCredential_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + rw := db.New(conn) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.PublicId, 1)[0] + lib := TestCredentialLibraries(t, conn, wrapper, cs.PublicId, 1)[0] + token := cs.Token() + + iamRepo := iam.TestRepo(t, conn, wrapper) + session := temp.TestDefaultSession(t, conn, wrapper, iamRepo) + + type args struct { + libraryId string + sessionId string + externalId string + tokenHmac []byte + expiration time.Duration + } + + tests := []struct { + name string + args args + want *Credential + wantErr bool + }{ + { + name: "missing-library-id", + args: args{ + sessionId: session.GetPublicId(), + externalId: "some/vault/credential", + tokenHmac: token.GetTokenHmac(), + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "missing-session-id", + args: args{ + libraryId: lib.GetPublicId(), + externalId: "some/vault/credential", + tokenHmac: token.GetTokenHmac(), + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "missing-tokenHmac", + args: args{ + libraryId: lib.GetPublicId(), + sessionId: session.GetPublicId(), + externalId: "some/vault/credential", + tokenHmac: []byte{}, + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "valid", + args: args{ + libraryId: lib.GetPublicId(), + sessionId: session.GetPublicId(), + externalId: "some/vault/credential", + tokenHmac: token.GetTokenHmac(), + expiration: 5 * time.Minute, + }, + want: &Credential{ + Credential: &store.Credential{ + LibraryId: lib.GetPublicId(), + SessionId: session.GetPublicId(), + ExternalId: "some/vault/credential", + TokenHmac: token.GetTokenHmac(), + Status: string(ActiveCredential), + }, + }, + }, + { + name: "valid-no-external-id", + args: args{ + libraryId: lib.GetPublicId(), + sessionId: session.GetPublicId(), + externalId: "", + tokenHmac: token.GetTokenHmac(), + expiration: 5 * time.Minute, + }, + want: &Credential{ + Credential: &store.Credential{ + LibraryId: lib.GetPublicId(), + SessionId: session.GetPublicId(), + ExternalId: "\ufffenone", + TokenHmac: token.GetTokenHmac(), + Status: string(UnknownCredentialStatus), + }, + }, + }, + { + name: "valid-no-expiration", + args: args{ + libraryId: lib.GetPublicId(), + sessionId: session.GetPublicId(), + externalId: "some/vault/credential", + tokenHmac: token.GetTokenHmac(), + expiration: 0, + }, + want: &Credential{ + Credential: &store.Credential{ + LibraryId: lib.GetPublicId(), + SessionId: session.GetPublicId(), + ExternalId: "some/vault/credential", + TokenHmac: token.GetTokenHmac(), + ExpirationTime: timestamp.New(db.PositiveInfinityTS), + Status: string(ActiveCredential), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + got, err := newCredential(tt.args.libraryId, tt.args.sessionId, + tt.args.externalId, tt.args.tokenHmac, tt.args.expiration) + if tt.wantErr { + assert.Error(err) + require.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + + assert.Emptyf(got.PublicId, "PublicId set") + + id, err := newCredentialId() + assert.NoError(err) + + tt.want.PublicId = id + got.PublicId = id + + query, queryValues := got.insertQuery() + + rows, err2 := rw.Exec(ctx, query, queryValues) + assert.Equal(1, rows) + assert.NoError(err2) + + got2 := allocCredential() + got2.PublicId = id + assert.Equal(id, got2.GetPublicId()) + require.NoError(rw.LookupById(ctx, got2)) + + tt.want.LastRenewalTime = got2.LastRenewalTime + tt.want.CreateTime = got2.CreateTime + tt.want.UpdateTime = got2.UpdateTime + tt.want.Version = got2.Version + if tt.want.ExpirationTime == nil { + tt.want.ExpirationTime = got2.ExpirationTime + } + + assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform())) + }) + } +} diff --git a/internal/credential/vault/doc.go b/internal/credential/vault/doc.go new file mode 100644 index 000000000..1fe01d582 --- /dev/null +++ b/internal/credential/vault/doc.go @@ -0,0 +1,3 @@ +// Package vault provides access to credentials retrieved from a Vault +// server. +package vault diff --git a/internal/credential/vault/docker.go b/internal/credential/vault/docker.go new file mode 100644 index 000000000..ae7a4cb0c --- /dev/null +++ b/internal/credential/vault/docker.go @@ -0,0 +1,18 @@ +package vault + +import "testing" + +var ( + newVaultServer func(t *testing.T, opt ...TestOption) *TestVaultServer = skipNewServer + mountDatabase func(t *testing.T, v *TestVaultServer, opt ...TestOption) *TestDatabase = skipMountDatabase +) + +func skipNewServer(t *testing.T, opt ...TestOption) *TestVaultServer { + t.Skip("docker not available") + return nil +} + +func skipMountDatabase(t *testing.T, v *TestVaultServer, opt ...TestOption) *TestDatabase { + t.Skip("docker not available") + return nil +} diff --git a/internal/credential/vault/fields.go b/internal/credential/vault/fields.go new file mode 100644 index 000000000..bd2415dd8 --- /dev/null +++ b/internal/credential/vault/fields.go @@ -0,0 +1,20 @@ +package vault + +// These constants are the field names used in the vault related field masks. +const ( + nameField = "Name" + descriptionField = "Description" + + vaultPathField = "VaultPath" + httpMethodField = "HttpMethod" + httpRequestBodyField = "HttpRequestBody" + + certificateField = "Certificate" + certificateKeyField = "CertificateKey" + vaultAddressField = "VaultAddress" + namespaceField = "Namespace" + caCertField = "CaCert" + tlsServerNameField = "TlsServerName" + tlsSkipVerifyField = "TlsSkipVerify" + tokenField = "Token" +) diff --git a/internal/credential/vault/jobs.go b/internal/credential/vault/jobs.go new file mode 100644 index 000000000..54638152d --- /dev/null +++ b/internal/credential/vault/jobs.go @@ -0,0 +1,975 @@ +package vault + +import ( + "context" + "net/http" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/go-hclog" + vault "github.com/hashicorp/vault/api" + ua "go.uber.org/atomic" +) + +const ( + tokenRenewalJobName = "vault_token_renewal" + tokenRevocationJobName = "vault_token_revocation" + credentialRenewalJobName = "vault_credential_renewal" + credentialRevocationJobName = "vault_credential_revocation" + credentialStoreCleanupJobName = "vault_credential_store_cleanup" + credentialCleanupJobName = "vault_credential_cleanup" + + defaultNextRunIn = 5 * time.Minute + renewalWindow = 10 * time.Minute +) + +func RegisterJobs(ctx context.Context, scheduler *scheduler.Scheduler, r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger) error { + const op = "vault.RegisterJobs" + tokenRenewal, err := newTokenRenewalJob(r, w, kms, logger) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, tokenRenewal); err != nil { + return errors.Wrap(err, op, errors.WithMsg("token renewal job")) + } + tokenRevoke, err := newTokenRevocationJob(r, w, kms, logger) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, tokenRevoke); err != nil { + return errors.Wrap(err, op, errors.WithMsg("token revocation job")) + } + credRenewal, err := newCredentialRenewalJob(r, w, kms, logger) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, credRenewal); err != nil { + return errors.Wrap(err, op, errors.WithMsg("credential renewal job")) + } + credRevoke, err := newCredentialRevocationJob(r, w, kms, logger) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, credRevoke); err != nil { + return errors.Wrap(err, op, errors.WithMsg("credential revocation job")) + } + credStoreCleanup, err := newCredentialStoreCleanupJob(r, w, kms, logger) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, credStoreCleanup); err != nil { + return errors.Wrap(err, op, errors.WithMsg("credential store cleanup job")) + } + credCleanup, err := newCredentialCleanupJob(w) + if err != nil { + return errors.Wrap(err, op) + } + if err = scheduler.RegisterJob(ctx, credCleanup); err != nil { + return errors.Wrap(err, op, errors.WithMsg("credential cleanup job")) + } + return nil +} + +// TokenRenewalJob is the recurring job that renews credential store Vault tokens that +// are in the `current` and `maintaining` state. The TokenRenewalJob is not thread safe, +// an attempt to Run the job concurrently will result in an JobAlreadyRunning error. +type TokenRenewalJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + logger hclog.Logger + limit int + + running ua.Bool + numTokens int + numProcessed int +} + +// newTokenRenewalJob creates a new in-memory TokenRenewalJob. +// +// WithLimit is the only supported option. +func newTokenRenewalJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*TokenRenewalJob, error) { + const op = "vault.newTokenRenewalJob" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing kms") + case logger == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing logger") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &TokenRenewalJob{ + reader: r, + writer: w, + kms: kms, + logger: logger, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the token renewal job. Total is the total number +// of tokens that are set to be renewed. Completed is the number of tokens already renewed. +func (r *TokenRenewalJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numTokens, + } +} + +// Run queries the vault credential repo for tokens that need to be renewed, it then creates +// a vault client and renews each token. Can not be run in parallel, if Run is invoked while +// already running an error with code JobAlreadyRunning will be returned. +func (r *TokenRenewalJob) Run(ctx context.Context) error { + const op = "vault.(TokenRenewalJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + var ps []*privateStore + // Fetch all tokens that will reach their renewal point within the renewalWindow. + // This is done to avoid constantly scheduling the token renewal job when there are multiple tokens + // set to renew in sequence. + err := r.reader.SearchWhere(ctx, &ps, `token_renewal_time < wt_add_seconds_to_now(?)`, []interface{}{renewalWindow.Seconds()}, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(err, op) + } + + // Set numProcessed and numTokens for status report + r.numProcessed, r.numTokens = 0, len(ps) + + for _, s := range ps { + // Verify context is not done before renewing next token + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + if err := r.renewToken(ctx, s); err != nil { + r.logger.Error("error renewing token", "credential store id", s.StoreId, "token status", s.TokenStatus, "error", err) + } + r.numProcessed++ + } + + return nil +} + +func (r *TokenRenewalJob) renewToken(ctx context.Context, s *privateStore) error { + const op = "vault.(TokenRenewalJob).renewToken" + databaseWrapper, err := r.kms.GetWrapper(ctx, s.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + if err = s.decrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(err, op) + } + + token := s.token() + if token == nil { + // Store has no token to renew + return nil + } + + vc, err := s.client() + if err != nil { + return errors.Wrap(err, op) + } + + var respErr *vault.ResponseError + renewedToken, err := vc.renewToken() + if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusForbidden { + // Vault returned a 403 when attempting a renew self, the token is either expired + // or malformed. Set status to "expired" so credentials created with token can be + // cleaned up. + query, values := token.updateStatusQuery(ExpiredToken) + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "token expired but failed to update repo") + } + if s.TokenStatus == string(CurrentToken) { + r.logger.Info("Vault credential store current token has expired", "credential store id", s.StoreId) + } + + // Set credentials associated with this token to expired as Vault will already cascade delete them + _, err = r.writer.Exec(ctx, updateCredentialStatusByTokenQuery, []interface{}{ExpiredCredential, token.TokenHmac}) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("error updating credentials to revoked after revoking token")) + } + + return nil + } + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to renew vault token")) + } + + tokenExpires, err := renewedToken.TokenTTL() + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration")) + } + + token.expiration = tokenExpires + query, values := token.updateExpirationQuery() + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "token renewed but failed to update repo") + } + + return nil +} + +// NextRunIn queries the vault credential repo to determine when the next token renewal job should run. +func (r *TokenRenewalJob) NextRunIn() (time.Duration, error) { + const op = "vault.(TokenRenewalJob).NextRunIn" + next, err := nextRenewal(r) + if err != nil { + return defaultNextRunIn, errors.Wrap(err, op) + } + + return next, nil +} + +func nextRenewal(j scheduler.Job) (time.Duration, error) { + const op = "vault.nextRenewal" + var query string + var r db.Reader + switch job := j.(type) { + case *TokenRenewalJob: + query = tokenRenewalNextRunInQuery + r = job.reader + case *CredentialRenewalJob: + query = credentialRenewalNextRunInQuery + r = job.reader + default: + return 0, errors.New(errors.Unknown, op, "unknown job") + } + + rows, err := r.Query(context.Background(), query, nil) + if err != nil { + return 0, errors.Wrap(err, op) + } + defer rows.Close() + + for rows.Next() { + type NextRenewal struct { + RenewalIn time.Duration + } + var n NextRenewal + err = r.ScanRows(rows, &n) + if err != nil { + return 0, errors.Wrap(err, op) + } + if n.RenewalIn < 0 { + // If we are past the next renewal time, return 0 to schedule immediately + return 0, nil + } + return n.RenewalIn * time.Second, nil + } + + return defaultNextRunIn, nil +} + +// Name is the unique name of the job. +func (r *TokenRenewalJob) Name() string { + return tokenRenewalJobName +} + +// Description is the human readable description of the job. +func (r *TokenRenewalJob) Description() string { + return "Periodically renews Vault credential store tokens that are in a maintaining or current state." +} + +// TokenRevocationJob is the recurring job that revokes credential store Vault tokens that +// are in the `maintaining` state and have no credentials being used by an active or pending session. +// The TokenRevocationJob is not thread safe, an attempt to Run the job concurrently will result in +// an JobAlreadyRunning error. +type TokenRevocationJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + logger hclog.Logger + limit int + + running ua.Bool + numTokens int + numProcessed int +} + +// newTokenRevocationJob creates a new in-memory TokenRevocationJob. +// +// WithLimit is the only supported option. +func newTokenRevocationJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*TokenRevocationJob, error) { + const op = "vault.newTokenRevocationJob" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing kms") + case logger == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing logger") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &TokenRevocationJob{ + reader: r, + writer: w, + kms: kms, + logger: logger, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the token revocation job. Total is the total number +// of tokens that are set to be revoked. Completed is the number of tokens already revoked. +func (r *TokenRevocationJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numTokens, + } +} + +// Run queries the vault credential repo for tokens that need to be revoked, it then creates +// a vault client and revokes each token. Can not be run in parallel, if Run is invoked while +// already running an error with code JobAlreadyRunning will be returned. +func (r *TokenRevocationJob) Run(ctx context.Context) error { + const op = "vault.(TokenRevocationJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + // Fetch all tokens in the revoke state as well as all tokens in the maintaining state + // that have no credentials in an active state. + where := ` +token_status = 'revoke' +or +(token_status = 'maintaining' + and token_hmac not in ( + select token_hmac from credential_vault_credential + where status = 'active' +)) +` + + var ps []*privateStore + err := r.reader.SearchWhere(ctx, &ps, where, nil, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(err, op) + } + + // Set numProcessed and numTokens for s report + r.numProcessed, r.numTokens = 0, len(ps) + for _, s := range ps { + // Verify context is not done before renewing next token + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + if err := r.revokeToken(ctx, s); err != nil { + r.logger.Error("error revoking token", "credential store id", s.StoreId, "error", err) + } + r.numProcessed++ + } + + return nil +} + +func (r *TokenRevocationJob) revokeToken(ctx context.Context, s *privateStore) error { + const op = "vault.(TokenRevocationJob).revokeToken" + databaseWrapper, err := r.kms.GetWrapper(ctx, s.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + if err = s.decrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(err, op) + } + + token := s.token() + if token == nil { + // Store has no token to revoke + return nil + } + + vc, err := s.client() + if err != nil { + return errors.Wrap(err, op) + } + + var respErr *vault.ResponseError + err = vc.revokeToken() + if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusForbidden { + // Vault returned a 403 when attempting a revoke self, the token is already expired. + // Clobber error and set status to "revoked" below. + err = nil + } + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to revoke vault token")) + } + + query, values := token.updateStatusQuery(RevokedToken) + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "token revoked but failed to update repo") + } + + // Set credentials associated with this token to revoked as Vault will already cascade revoke them + _, err = r.writer.Exec(ctx, updateCredentialStatusByTokenQuery, []interface{}{RevokedCredential, token.TokenHmac}) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("error updating credentials to revoked after revoking token")) + } + + return nil +} + +// NextRunIn determines when the next token revocation job should run. +func (r *TokenRevocationJob) NextRunIn() (time.Duration, error) { + return defaultNextRunIn, nil +} + +// Name is the unique name of the job. +func (r *TokenRevocationJob) Name() string { + return tokenRevocationJobName +} + +// Description is the human readable description of the job. +func (r *TokenRevocationJob) Description() string { + return "Periodically revokes Vault credential store tokens that are in a maintaining state and have no active credentials associated." +} + +// CredentialRenewalJob is the recurring job that renews Vault credentials issued to a session. +// The CredentialRenewalJob is not thread safe, an attempt to Run the job concurrently will result +// in an JobAlreadyRunning error. +type CredentialRenewalJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + logger hclog.Logger + limit int + + running ua.Bool + numCreds int + numProcessed int +} + +// newCredentialRenewalJob creates a new in-memory CredentialRenewalJob. +// +// WithLimit is the only supported option. +func newCredentialRenewalJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialRenewalJob, error) { + const op = "vault.newCredentialRenewalJob" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing kms") + case logger == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing logger") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &CredentialRenewalJob{ + reader: r, + writer: w, + kms: kms, + logger: logger, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the credential renewal job. Total is the total number +// of credentials that are set to be renewed. Completed is the number of credential already renewed. +func (r *CredentialRenewalJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numCreds, + } +} + +// Run queries the vault credential repo for credentials that need to be renewed, it then creates +// a vault client and renews each credential. Can not be run in parallel, if Run is invoked while +// already running an error with code JobAlreadyRunning will be returned. +func (r *CredentialRenewalJob) Run(ctx context.Context) error { + const op = "vault.(CredentialRenewalJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + var creds []*privateCredential + // Fetch all active credentials that will reach their renewal point within the renewalWindow. + // This is done to avoid constantly scheduling the credential renewal job when there are + // multiple credentials set to renew in sequence. + err := r.reader.SearchWhere(ctx, &creds, `renewal_time < wt_add_seconds_to_now(?) and status = ?`, []interface{}{renewalWindow.Seconds(), ActiveCredential}, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(err, op) + } + + // Set numProcessed and numTokens for status report + r.numProcessed, r.numCreds = 0, len(creds) + for _, c := range creds { + // Verify context is not done before renewing next token + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + if err := r.renewCred(ctx, c); err != nil { + r.logger.Error("error renewing credential", "credential id", c.PublicId, "error", err) + } + + r.numProcessed++ + } + + return nil +} + +func (r *CredentialRenewalJob) renewCred(ctx context.Context, c *privateCredential) error { + const op = "vault.(CredentialRenewalJob).renewCred" + databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + if err = c.decrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(err, op) + } + + vc, err := c.client() + if err != nil { + return errors.Wrap(err, op) + } + cred := c.toCredential() + + var respErr *vault.ResponseError + // Subtract last renewal time from previous expiration time to get lease duration + leaseDuration := c.ExpirationTime.AsTime().Sub(c.LastRenewalTime.AsTime()) + renewedCred, err := vc.renewLease(c.ExternalId, leaseDuration) + if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusBadRequest { + // Vault returned a 400 when attempting a renew lease, the lease is either expired + // or the leaseId is malformed. Set status to "expired". + query, values := cred.updateStatusQuery(ExpiredCredential) + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "credential expired but failed to update repo") + } + return nil + } + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to renew credential")) + } + + cred.expiration = time.Duration(renewedCred.LeaseDuration) * time.Second + query, values := cred.updateExpirationQuery() + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "credential renewed but failed to update repo") + } + + return nil +} + +// NextRunIn queries the vault credential repo to determine when the next credential renewal job should run. +func (r *CredentialRenewalJob) NextRunIn() (time.Duration, error) { + const op = "vault.(CredentialRenewalJob).NextRunIn" + next, err := nextRenewal(r) + if err != nil { + return defaultNextRunIn, errors.Wrap(err, op) + } + + return next, nil +} + +// Name is the unique name of the job. +func (r *CredentialRenewalJob) Name() string { + return credentialRenewalJobName +} + +// Description is the human readable description of the job. +func (r *CredentialRenewalJob) Description() string { + return "Periodically renews Vault credentials that are attached to an active/pending session (in the active state)." +} + +// CredentialRevocationJob is the recurring job that revokes Vault credentials that are no +// longer being used by an active or pending session. +// The CredentialRevocationJob is not thread safe, an attempt to Run the job concurrently +// will result in an JobAlreadyRunning error. +type CredentialRevocationJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + logger hclog.Logger + limit int + + running ua.Bool + numCreds int + numProcessed int +} + +// newCredentialRevocationJob creates a new in-memory CredentialRevocationJob. +// +// WithLimit is the only supported option. +func newCredentialRevocationJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialRevocationJob, error) { + const op = "vault.newCredentialRevocationJob" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing kms") + case logger == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing logger") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &CredentialRevocationJob{ + reader: r, + writer: w, + kms: kms, + logger: logger, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the credential revocation job. Total is the total number +// of credentials that are set to be revoked. Completed is the number of credentials already revoked. +func (r *CredentialRevocationJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numCreds, + } +} + +// Run queries the vault credential repo for credentials that need to be revoked, it then creates +// a vault client and revokes each credential. Can not be run in parallel, if Run is invoked while +// already running an error with code JobAlreadyRunning will be returned. +func (r *CredentialRevocationJob) Run(ctx context.Context) error { + const op = "vault.(CredentialRevocationJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + var creds []*privateCredential + err := r.reader.SearchWhere(ctx, &creds, "status = ?", []interface{}{RevokeCredential}, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(err, op) + } + + // Set numProcessed and numTokens for status report + r.numProcessed, r.numCreds = 0, len(creds) + for _, c := range creds { + // Verify context is not done before renewing next token + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + if err := r.revokeCred(ctx, c); err != nil { + r.logger.Error("error revoking credential", "credential id", c.PublicId, "error", err) + } + r.numProcessed++ + } + + return nil +} + +func (r *CredentialRevocationJob) revokeCred(ctx context.Context, c *privateCredential) error { + const op = "vault.(CredentialRenewalJob).revokeCred" + databaseWrapper, err := r.kms.GetWrapper(ctx, c.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + if err = c.decrypt(ctx, databaseWrapper); err != nil { + return errors.Wrap(err, op) + } + + vc, err := c.client() + if err != nil { + return errors.Wrap(err, op) + } + + cred := c.toCredential() + var respErr *vault.ResponseError + err = vc.revokeLease(c.ExternalId) + if ok := errors.As(err, &respErr); ok && respErr.StatusCode == http.StatusBadRequest { + // Vault returned a 400 when attempting a revoke lease, the lease is already expired. + // Clobber error and set status to "revoked" below. + err = nil + } + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to revoke credential")) + } + + query, values := cred.updateStatusQuery(RevokedCredential) + numRows, err := r.writer.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if numRows != 1 { + return errors.New(errors.Unknown, op, "credential revoked but failed to update repo") + } + + return nil +} + +// NextRunIn determine when the next credential revocation job should run. +func (r *CredentialRevocationJob) NextRunIn() (time.Duration, error) { + return defaultNextRunIn, nil +} + +// Name is the unique name of the job. +func (r *CredentialRevocationJob) Name() string { + return credentialRevocationJobName +} + +// Description is the human readable description of the job. +func (r *CredentialRevocationJob) Description() string { + return "Periodically revokes dynamic credentials that are no longer in use and have been set for revocation (in the revoke state)." +} + +// CredentialStoreCleanupJob is the recurring job that deletes Vault credential stores that +// have been soft deleted and tokens have been revoked or expired. +// The CredentialStoreCleanupJob is not thread safe, an attempt to Run the job concurrently +// will result in an JobAlreadyRunning error. +type CredentialStoreCleanupJob struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + logger hclog.Logger + + limit int + running ua.Bool + numProcessed int + numStores int +} + +// newCredentialStoreCleanupJob creates a new in-memory CredentialStoreCleanupJob. +// +// No options are supported. +func newCredentialStoreCleanupJob(r db.Reader, w db.Writer, kms *kms.Kms, logger hclog.Logger, opt ...Option) (*CredentialStoreCleanupJob, error) { + const op = "vault.newCredentialStoreCleanupJob" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing kms") + case logger == nil: + return nil, errors.New(errors.InvalidParameter, op, "missing logger") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + return &CredentialStoreCleanupJob{ + reader: r, + writer: w, + kms: kms, + logger: logger, + limit: opts.withLimit, + }, nil +} + +// Status returns the current status of the credential store cleanup job. +func (r *CredentialStoreCleanupJob) Status() scheduler.JobStatus { + return scheduler.JobStatus{ + Completed: r.numProcessed, + Total: r.numStores, + } +} + +// Run deletes all vault credential stores in the repo that have been soft deleted. +// Can not be run in parallel, if Run is invoked while already running an error with code +// JobAlreadyRunning will be returned. +func (r *CredentialStoreCleanupJob) Run(ctx context.Context) error { + const op = "vault.(CredentialStoreCleanupJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + // TODO (lcr 06/2021): Oplog does not currently support bulk + // operations. Push cleanup to the database once bulk + // operations are added. + var stores []*CredentialStore + err := r.reader.SearchWhere(ctx, &stores, credStoreCleanupWhereClause, []interface{}{RevokeToken}, db.WithLimit(r.limit)) + if err != nil { + return errors.Wrap(err, op) + } + + // Set numProcessed and numStores for status report + r.numProcessed, r.numStores = 0, len(stores) + for _, store := range stores { + // Verify context is not done before renewing next token + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, store.ScopeId, kms.KeyPurposeOplog) + if err != nil { + r.logger.Error("unable to get oplog wrapper for credential store cleanup job", "credential store id", store.PublicId) + r.numProcessed++ + continue + } + + _, err = r.writer.Delete(ctx, store, db.WithOplog(oplogWrapper, store.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err != nil { + r.logger.Error("error deleting credential store", "error", err, "credential store id", store.PublicId) + } + + r.numProcessed++ + } + + return nil +} + +// NextRunIn determine when the next credential store cleanup job should run. +func (r *CredentialStoreCleanupJob) NextRunIn() (time.Duration, error) { + return defaultNextRunIn, nil +} + +// Name is the unique name of the job. +func (r *CredentialStoreCleanupJob) Name() string { + return credentialStoreCleanupJobName +} + +// Description is the human readable description of the job. +func (r *CredentialStoreCleanupJob) Description() string { + return "Periodically deletes Vault credential stores that have been soft deleted and tokens have been revoked or expired." +} + +// CredentialCleanupJob is the recurring job that deletes Vault credentials that are no longer +// attached to a session (have a null session_id) and are not active. +// The CredentialCleanupJob is not thread safe, an attempt to Run the job concurrently +// will result in an JobAlreadyRunning error. +type CredentialCleanupJob struct { + writer db.Writer + + running ua.Bool + numCreds int +} + +// newCredentialCleanupJob creates a new in-memory CredentialCleanupJob. +// +// No options are supported. +func newCredentialCleanupJob(w db.Writer) (*CredentialCleanupJob, error) { + const op = "vault.newCredentialCleanupJob" + if w == nil { + return nil, errors.New(errors.InvalidParameter, op, "missing db.Writer") + } + + return &CredentialCleanupJob{ + writer: w, + }, nil +} + +// Status returns the current status of the credential cleanup job. +func (r *CredentialCleanupJob) Status() scheduler.JobStatus { + // Cleanup runs a single exec command to the database, therefore completed and total + // are both set to numCreds. + return scheduler.JobStatus{ + Completed: r.numCreds, + Total: r.numCreds, + } +} + +// Run deletes all Vault credential in the repo that have a null session_id and are not active. +// Can not be run in parallel, if Run is invoked while already running an error with code +// JobAlreadyRunning will be returned. +func (r *CredentialCleanupJob) Run(ctx context.Context) error { + const op = "vault.(CredentialCleanupJob).Run" + if !r.running.CAS(r.running.Load(), true) { + return errors.New(errors.JobAlreadyRunning, op, "job already running") + } + defer r.running.Store(false) + + // Verify context is not done before running + if err := ctx.Err(); err != nil { + return errors.Wrap(err, op) + } + + numRows, err := r.writer.Exec(ctx, credCleanupQuery, nil) + if err != nil { + return errors.Wrap(err, op) + } + r.numCreds = numRows + + return nil +} + +// NextRunIn determine when the next credential cleanup job should run. +func (r *CredentialCleanupJob) NextRunIn() (time.Duration, error) { + return defaultNextRunIn, nil +} + +// Name is the unique name of the job. +func (r *CredentialCleanupJob) Name() string { + return credentialCleanupJobName +} + +// Description is the human readable description of the job. +func (r *CredentialCleanupJob) Description() string { + return "Periodically deletes Vault credentials that are no longer attached to a session (have a null session_id) and are not active in Vault." +} diff --git a/internal/credential/vault/jobs_test.go b/internal/credential/vault/jobs_test.go new file mode 100644 index 000000000..73f5eccfb --- /dev/null +++ b/internal/credential/vault/jobs_test.go @@ -0,0 +1,2272 @@ +package vault + +import ( + "context" + "path" + "strings" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/boundary/internal/session" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/go-hclog" + wrapping "github.com/hashicorp/go-kms-wrapping" + vault "github.com/hashicorp/vault/api" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testUpdateTokenStatusExpirationQuery = ` +update credential_vault_token + set status = ?, + last_renewal_time = now(), + expiration_time = wt_add_seconds_to_now(?) + where token_hmac = ?; +` + +func testVaultToken(t *testing.T, + conn *gorm.DB, + wrapper wrapping.Wrapper, + v *TestVaultServer, + cs *CredentialStore, + status TokenStatus, + expiration time.Duration) *Token { + t.Helper() + assert, require := assert.New(t), require.New(t) + kmsCache := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + secret, _ := v.CreateToken(t) + inToken, err := newToken(cs.PublicId, []byte(secret.Auth.ClientToken), []byte(secret.Auth.Accessor), expiration) + require.NoError(err) + inToken.Status = string(status) + + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), cs.ScopeId, kms.KeyPurposeDatabase) + require.NoError(err) + + require.NoError(inToken.encrypt(context.Background(), databaseWrapper)) + + query := insertTokenQuery + queryValues := []interface{}{ + inToken.TokenHmac, + inToken.CtToken, + inToken.StoreId, + inToken.KeyId, + inToken.Status, + } + expire := int(expiration.Seconds()) + if expire < 0 { + // last_renewal_time must be before expiration_time, if we are testing a expiration + // in the past set last_renewal_time to 1 second before that + query = strings.Replace(query, + "$6, -- last_renewal_time", + "wt_add_seconds_to_now($6), -- last_renewal_time", + -1) + queryValues = append(queryValues, expire-1, expire) + } else { + queryValues = append(queryValues, "now()", expire) + } + + numRows, err := rw.Exec(context.Background(), query, queryValues) + assert.Equal(1, numRows) + require.NoError(err) + + outToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &outToken, "token_hmac = ?", inToken.TokenHmac)) + require.NoError(outToken.decrypt(context.Background(), databaseWrapper)) + + return outToken +} + +func testVaultCred(t *testing.T, + conn *gorm.DB, + v *TestVaultServer, + cl *CredentialLibrary, + sess *session.Session, + token *Token, + status CredentialStatus, + expiration time.Duration) (*vault.Secret, *Credential) { + t.Helper() + assert, require := assert.New(t), require.New(t) + rw := db.New(conn) + + client := v.client(t) + var secret *vault.Secret + var err error + switch Method(cl.HttpMethod) { + case MethodGet: + secret, err = client.get(cl.VaultPath) + case MethodPost: + secret, err = client.post(cl.VaultPath, cl.HttpRequestBody) + } + require.NoError(err) + require.NotNil(secret) + + id, err := newCredentialId() + require.NoError(err) + + query := insertCredentialWithExpirationQuery + queryValues := []interface{}{ + id, + cl.GetPublicId(), + sess.GetPublicId(), + token.GetTokenHmac(), + secret.LeaseID, + true, + status, + } + expire := int(expiration.Seconds()) + if expire < 0 { + // last_renewal_time must be before expiration_time, if we are testing a expiration + // in the past set last_renewal_time to 1 second before that + query = strings.Replace(query, + "$8, -- last_renewal_time", + "wt_add_seconds_to_now($8), -- last_renewal_time", + -1) + queryValues = append(queryValues, expire-1, expire) + } else { + queryValues = append(queryValues, "now()", expire) + } + + numRows, err := rw.Exec(context.Background(), query, queryValues) + assert.Equal(1, numRows) + assert.NoError(err) + + outCred := allocCredential() + require.NoError(rw.LookupWhere(context.Background(), &outCred, "public_id = ?", id)) + + return secret, outCred +} + +func TestNewTokenRenewalJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + logger hclog.Logger + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil logger", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newTokenRenewalJob(tt.args.r, tt.args.w, tt.args.kms, tt.args.logger, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + require.NotNil(got.logger) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestTokenRenewalJob_RunLimits(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t) + + count := 5 + tests := []struct { + name string + opts []Option + wantLen int + }{ + { + name: "with-no-limits", + wantLen: count + 1, // +1 for current token + }, + { + name: "with-negative-limit", + opts: []Option{WithLimit(-1)}, + wantLen: count + 1, // +1 for current token + }, + { + name: "with-limit", + opts: []Option{WithLimit(2)}, + wantLen: 2, + }, + { + name: "with-limit-greater-than-count", + opts: []Option{WithLimit(count + 10)}, + wantLen: count + 1, // +1 for current token + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, token := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + // Create additional tokens and alternative between token statuses, revoked and + // expired tokens should have no impact on number tokens renewed + for i := 0; i < count*3; i++ { + var status TokenStatus + switch i % 3 { + case 0: + status = MaintainingToken + case 1: + status = RevokedToken + case 2: + status = ExpiredToken + } + testVaultToken(t, conn, wrapper, v, cs, status, 5*time.Minute) + } + + // inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time + numRows, err := rw.Exec(context.Background(), testUpdateTokenStatusExpirationQuery, []interface{}{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, numRows) + + r, err := newTokenRenewalJob(rw, rw, kmsCache, hclog.L(), tt.opts...) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(tt.wantLen, r.numTokens) + + // Set all tokens to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_token set status = 'revoked'", nil) + assert.NoError(err) + }) + } +} + +func TestTokenRenewalJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t) + + // Create 24 hour token + _, token := v.CreateToken(t, WithTokenPeriod(24*time.Hour)) + + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + r, err := newTokenRenewalJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + // No tokens should have been renewed since token expiration is 24 hours by default + assert.Equal(0, r.numProcessed) + + // Create maintaining, revoked and expired tokens + maintainToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, time.Minute) + revokedToken := testVaultToken(t, conn, wrapper, v, cs, RevokedToken, time.Minute) + expiredToken := testVaultToken(t, conn, wrapper, v, cs, ExpiredToken, time.Minute) + + // inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time + count, err := rw.Exec(context.Background(), testUpdateTokenStatusExpirationQuery, []interface{}{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + + currentToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), ¤tToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), cs.ScopeId, kms.KeyPurposeDatabase) + require.NoError(err) + require.NoError(currentToken.decrypt(context.Background(), databaseWrapper)) + + // Sleep to move clock + time.Sleep(time.Second * 2) + + // Get ttls in vault to verify against before running renewal + lookupToken := v.LookupToken(t, string(currentToken.GetToken())) + oldCurrentTtl, err := lookupToken.TokenTTL() + require.NoError(err) + lookupToken = v.LookupToken(t, string(maintainToken.GetToken())) + oldMaintainTtl, err := lookupToken.TokenTTL() + require.NoError(err) + lookupToken = v.LookupToken(t, string(revokedToken.GetToken())) + oldRevokedTtl, err := lookupToken.TokenTTL() + require.NoError(err) + lookupToken = v.LookupToken(t, string(expiredToken.GetToken())) + oldExpiredTtl, err := lookupToken.TokenTTL() + require.NoError(err) + + // Run token renewal again + err = r.Run(context.Background()) + require.NoError(err) + // Current and maintaining token should have been processed + assert.Equal(2, r.numProcessed) + + // Verify current and maintaining token were renewed in vault + lookupToken = v.LookupToken(t, string(currentToken.GetToken())) + newTtl, err := lookupToken.TokenTTL() + require.NoError(err) + assert.True(oldCurrentTtl < newTtl) + lookupToken = v.LookupToken(t, string(maintainToken.GetToken())) + newTtl, err = lookupToken.TokenTTL() + require.NoError(err) + assert.True(oldMaintainTtl < newTtl) + + // Verify expired and revoked tokens were not renewed in vault + lookupToken = v.LookupToken(t, string(revokedToken.GetToken())) + newTtl, err = lookupToken.TokenTTL() + require.NoError(err) + assert.True(oldRevokedTtl >= newTtl) + lookupToken = v.LookupToken(t, string(expiredToken.GetToken())) + newTtl, err = lookupToken.TokenTTL() + require.NoError(err) + assert.True(oldExpiredTtl >= newTtl) + + // Verify current and maintaining tokens were renewed in repo + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{currentToken.TokenHmac})) + assert.True(currentToken.GetExpirationTime().AsTime().Before(repoToken.GetExpirationTime().AsTime())) + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{maintainToken.TokenHmac})) + assert.True(maintainToken.GetExpirationTime().AsTime().Before(repoToken.GetExpirationTime().AsTime())) + + // Verify revoked and expired tokens were not renewed in the repo + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{revokedToken.TokenHmac})) + assert.Equal(revokedToken.GetExpirationTime().AsTime(), repoToken.GetExpirationTime().AsTime()) + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{expiredToken.TokenHmac})) + assert.Equal(expiredToken.GetExpirationTime().AsTime(), repoToken.GetExpirationTime().AsTime()) +} + +func TestTokenRenewalJob_RunExpired(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t) + + // Create 1s token so it expires in vault before we can renew it + _, ct := v.CreateToken(t, WithTokenPeriod(time.Second)) + + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + assert.NoError(err) + require.NotNil(in) + + r, err := newTokenRenewalJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + // Sleep to move clock and expire token + time.Sleep(time.Second * 2) + + // Token should have expired in vault, run should now expire in repo + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(1, r.numTokens) + + // Verify token was expired in repo + token := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &token, "store_id = ?", []interface{}{cs.GetPublicId()})) + assert.Equal(string(ExpiredToken), token.Status) +} + +func TestTokenRenewalJob_NextRunIn(t *testing.T) { + t.Parallel() + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t) + + type tokenArgs struct { + e time.Duration + s TokenStatus + } + tests := []struct { + name string + currentTokenExp time.Duration + tokens []tokenArgs + want time.Duration + skipCredStore bool + }{ + { + name: "default-duration", + skipCredStore: true, + want: defaultNextRunIn, + }, + { + name: "1-hour-token", + currentTokenExp: time.Hour, + want: 30 * time.Minute, + }, + { + name: "2-hour-token", + currentTokenExp: 2 * time.Hour, + want: time.Hour, + }, + { + name: "multiple-maintaining", + currentTokenExp: 24 * time.Hour, + tokens: []tokenArgs{ + {e: 12 * time.Hour, s: MaintainingToken}, + {e: 6 * time.Hour, s: MaintainingToken}, + {e: 8 * time.Hour, s: MaintainingToken}, + }, + // 6 hours is the soonest expiry + want: 3 * time.Hour, + }, + { + name: "multiple-maintaining-with-single-overdue-renewal", + currentTokenExp: 24 * time.Hour, + tokens: []tokenArgs{ + {e: 24 * time.Hour, s: MaintainingToken}, + {e: 6 * time.Hour, s: MaintainingToken}, + {e: -12 * time.Hour, s: MaintainingToken}, + {e: 10 * time.Hour, s: MaintainingToken}, + }, + want: 0, + }, + { + name: "multiple-current-soonest", + currentTokenExp: 6 * time.Hour, + tokens: []tokenArgs{ + {e: time.Minute, s: RevokedToken}, + {e: time.Minute, s: ExpiredToken}, + {e: 8 * time.Hour, s: MaintainingToken}, + }, + // 6 hours is the soonest expiry, revoked and expired tokens with sooner + // expirations should be ignored + want: 3 * time.Hour, + }, + { + name: "multiple-maintaining-soonest", + currentTokenExp: 6 * time.Hour, + tokens: []tokenArgs{ + {e: time.Minute, s: RevokedToken}, + {e: time.Minute, s: ExpiredToken}, + {e: 4 * time.Hour, s: MaintainingToken}, + }, + // 4 hour maintaining token is the soonest expiry + // revoked and expired tokens with sooner expirations should be ignored + want: 2 * time.Hour, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + r, err := newTokenRenewalJob(rw, rw, kmsCache, hclog.L()) + assert.NoError(err) + require.NotNil(r) + + if !tt.skipCredStore { + _, token := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + for _, token := range tt.tokens { + testVaultToken(t, conn, wrapper, v, cs, token.s, token.e) + } + + // inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time + count, err := rw.Exec(context.Background(), testUpdateTokenStatusExpirationQuery, []interface{}{CurrentToken, tt.currentTokenExp.Seconds(), cs.outputToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + } + + got, err := r.NextRunIn() + require.NoError(err) + // Round to time.Minute to account for lost time between creating tokens and determining next run + assert.Equal(tt.want.Round(time.Minute), got.Round(time.Minute)) + + // Set all tokens to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_token set status = 'revoked'", nil) + assert.NoError(err) + }) + } +} + +func TestNewTokenRevocationJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + logger hclog.Logger + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil logger", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newTokenRevocationJob(tt.args.r, tt.args.w, tt.args.kms, tt.args.logger, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + require.NotNil(got.logger) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestTokenRevocationJob_RunLimits(t *testing.T) { + t.Parallel() + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t) + + count := 5 + tests := []struct { + name string + opts []Option + wantLen int + }{ + { + name: "with-no-limits", + wantLen: count, + }, + { + name: "with-negative-limit", + opts: []Option{WithLimit(-1)}, + wantLen: count, + }, + { + name: "with-limit", + opts: []Option{WithLimit(2)}, + wantLen: 2, + }, + { + name: "with-limit-greater-than-count", + opts: []Option{WithLimit(count + 5)}, + wantLen: count, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, token := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + for i := 0; i < count*3; i++ { + var status TokenStatus + // Alternative between token status, the current token as well as the + // revoked and expired tokens should have no impact on number tokens revoked + switch i % 3 { + case 0: + status = MaintainingToken + case 1: + status = RevokedToken + case 2: + status = ExpiredToken + } + testVaultToken(t, conn, wrapper, v, cs, status, 5*time.Minute) + } + + // inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time + numRows, err := rw.Exec(context.Background(), testUpdateTokenStatusExpirationQuery, []interface{}{CurrentToken, time.Minute.Seconds(), cs.outputToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, numRows) + + r, err := newTokenRevocationJob(rw, rw, kmsCache, hclog.L(), tt.opts...) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(tt.wantLen, r.numTokens) + + // Set all tokens to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_token set status = 'revoked'", nil) + require.NoError(err) + }) + } +} + +func TestTokenRevocationJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + _, ct := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + r, err := newTokenRevocationJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + // No tokens should have been revoked since only the current token exists + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numProcessed) + + // Create maintaining tokens with and without credentials + noCredsToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, 5*time.Minute) + credsToken := testVaultToken(t, conn, wrapper, v, cs, MaintainingToken, 5*time.Minute) + revokeToken := testVaultToken(t, conn, wrapper, v, cs, RevokeToken, 5*time.Minute) + + // inserting new tokens moves the current token to a maintaining state, move it back to current and set expiration time + count, err := rw.Exec(context.Background(), testUpdateTokenStatusExpirationQuery, []interface{}{CurrentToken, (5 * time.Minute).Seconds(), cs.outputToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + + // Create cred lib and session for credential + libPath := path.Join("database", "creds", "opened") + cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet)) + require.NoError(err) + cl.PublicId, err = newCredentialLibraryId() + require.NoError(err) + err = rw.Create(context.Background(), cl) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + // Create credential attached to credsToken + _, cred := testVaultCred(t, conn, v, cl, sess, credsToken, ActiveCredential, 5*time.Minute) + + // Create fake credential attached to revokeToken + _, revokeCred := testVaultCred(t, conn, v, cl, sess, revokeToken, ActiveCredential, 5*time.Minute) + + // Running should revoke noCredsToken and the revokeToken even though it has active + // credentials it has been marked for revocation + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(2, r.numProcessed) + + // Verify noCredsToken was revoked in vault + v.VerifyTokenInvalid(t, string(noCredsToken.GetToken())) + + // Verify noCredsToken was set to revoked in repo + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{noCredsToken.TokenHmac})) + assert.Equal(string(RevokedToken), repoToken.Status) + + // Verify revokeToken was revoked in vault + v.VerifyTokenInvalid(t, string(revokeToken.GetToken())) + + // Verify revokeToken was set to revoked in repo + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{revokeToken.TokenHmac})) + assert.Equal(string(RevokedToken), repoToken.Status) + + // Verify revokeCred attached to revokeToken were marked as revoked + lookupCred := allocCredential() + lookupCred.PublicId = revokeCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(RevokedCredential), lookupCred.Status) + + // Verify credsToken was not revoked in vault + lookupToken := v.LookupToken(t, string(credsToken.GetToken())) + assert.NotNil(lookupToken) + + // Revoke credential in repo + query, queryValues := cred.updateStatusQuery(RevokedCredential) + rows, err := rw.Exec(context.Background(), query, queryValues) + assert.Equal(1, rows) + assert.NoError(err) + + // Running again should now revoke the credsToken + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(1, r.numProcessed) + + // Verify credsToken was revoked in vault + v.VerifyTokenInvalid(t, string(credsToken.GetToken())) + + // Verify credsToken was set to revoked in repo + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{credsToken.TokenHmac})) + assert.Equal(string(RevokedToken), repoToken.Status) + + err = r.Run(context.Background()) + require.NoError(err) + // With only the current token remaining no tokens should be revoked + assert.Equal(0, r.numProcessed) +} + +func TestNewCredentialRenewalJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + logger hclog.Logger + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil logger", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newCredentialRenewalJob(tt.args.r, tt.args.w, tt.args.kms, tt.args.logger, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + require.NotNil(got.logger) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestCredentialRenewalJob_RunLimits(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + _, ct := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(t, err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(t, err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(t, err) + + libPath := path.Join("database", "creds", "opened") + cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet)) + require.NoError(t, err) + cl.PublicId, err = newCredentialLibraryId() + require.NoError(t, err) + err = rw.Create(context.Background(), cl) + require.NoError(t, err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + credsToken := testVaultToken(t, conn, wrapper, v, cs, CurrentToken, 5*time.Minute) + + count := 10 + tests := []struct { + name string + opts []Option + wantLen int + }{ + { + name: "with-no-limits", + wantLen: count, + }, + { + name: "with-negative-limit", + opts: []Option{WithLimit(-1)}, + wantLen: count, + }, + { + name: "with-limit", + opts: []Option{WithLimit(2)}, + wantLen: 2, + }, + { + name: "with-limit-greater-than-count", + opts: []Option{WithLimit(count + 5)}, + wantLen: count, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + for i := 0; i < count*4; i++ { + var status CredentialStatus + // Alternative between cred status, revoke, revoked and expired should have + // no impact on number creds renewed. Only active creds should be renewed + switch i % 4 { + case 0: + status = ActiveCredential + case 1: + status = RevokeCredential + case 2: + status = RevokedCredential + case 3: + status = ExpiredCredential + } + testVaultCred(t, conn, v, cl, sess, credsToken, status, 5*time.Minute) + } + + r, err := newCredentialRenewalJob(rw, rw, kmsCache, hclog.L(), tt.opts...) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(tt.wantLen, r.numCreds) + + // Set all credentials to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_credential set status = 'revoked'", nil) + assert.NoError(err) + }) + } +} + +func TestCredentialRenewalJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn) + require.NoError(err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + csToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &csToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + credRenewal, err := newCredentialRenewalJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = credRenewal.Run(context.Background()) + require.NoError(err) + // No credentials should have been renewed + assert.Equal(0, credRenewal.numCreds) + + _, activeCred := testVaultCred(t, conn, v, cl, sess, csToken, ActiveCredential, 5*time.Minute) + _, revokeCred := testVaultCred(t, conn, v, cl, sess, csToken, RevokeCredential, 5*time.Minute) + _, revokedCred := testVaultCred(t, conn, v, cl, sess, csToken, RevokedCredential, 5*time.Minute) + _, expiredCred := testVaultCred(t, conn, v, cl, sess, csToken, ExpiredCredential, 5*time.Minute) + + secret := v.LookupLease(t, activeCred.ExternalId) + // Secret should not have a last renewal time + assert.Nil(secret.Data["last_renewal"]) + + // Sleep to move clock + time.Sleep(2 * time.Second) + + err = credRenewal.Run(context.Background()) + require.NoError(err) + // The active credential should have been renewed + assert.Equal(1, credRenewal.numCreds) + + // Active credential expiration time should have been updated + lookupCred := allocCredential() + lookupCred.PublicId = activeCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Truef(lookupCred.ExpirationTime.AsTime().After(activeCred.ExpirationTime.AsTime()), "expected expiration time to be updated") + + // Revoke, Revoked and Expired credentials expiration times should not have changed + lookupCred = allocCredential() + lookupCred.PublicId = revokeCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(lookupCred.ExpirationTime.AsTime(), revokeCred.ExpirationTime.AsTime()) + lookupCred = allocCredential() + lookupCred.PublicId = revokedCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(lookupCred.ExpirationTime.AsTime(), revokedCred.ExpirationTime.AsTime()) + lookupCred = allocCredential() + lookupCred.PublicId = expiredCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(lookupCred.ExpirationTime.AsTime(), expiredCred.ExpirationTime.AsTime()) + + // Active credential should have a last renewal time in Vault + secret = v.LookupLease(t, activeCred.ExternalId) + assert.NotNil(secret.Data["last_renewal"]) + + // Revoke, Revoked and Expired credentials should not have a last renewal time + secret = v.LookupLease(t, revokeCred.ExternalId) + assert.Nil(secret.Data["last_renewal"]) + secret = v.LookupLease(t, revokedCred.ExternalId) + assert.Nil(secret.Data["last_renewal"]) + secret = v.LookupLease(t, expiredCred.ExternalId) + assert.Nil(secret.Data["last_renewal"]) +} + +func TestCredentialRenewalJob_RunExpired(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn) + require.NoError(err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + credRenewal, err := newCredentialRenewalJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + _, cred := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, time.Minute) + + vc := v.client(t).cl + // revoke the credential in Vault outside of the job + assert.NoError(vc.Sys().Revoke(cred.ExternalId)) + + // Credential status should still be active + lookupCred := allocCredential() + lookupCred.PublicId = cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(ActiveCredential), lookupCred.Status) + + err = credRenewal.Run(context.Background()) + require.NoError(err) + // The active credential should have been processed + assert.Equal(1, credRenewal.numCreds) + + // Credential status should have been updated to expired + lookupCred = allocCredential() + lookupCred.PublicId = cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(ExpiredCredential), lookupCred.Status) +} + +func TestCredentialRenewalJob_NextRunIn(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + _, ct := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(t, err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(t, err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(t, err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(t, err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(t, err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + type args struct { + e time.Duration + s CredentialStatus + } + tests := []struct { + name string + args []args + want time.Duration + }{ + { + name: "default-duration", + want: defaultNextRunIn, + }, + { + name: "1-hour-active-credential", + args: []args{ + {s: ActiveCredential, e: time.Hour}, + }, + want: 30 * time.Minute, + }, + { + name: "1-hour-revoke-credential", + args: []args{ + {s: RevokeCredential, e: time.Hour}, + }, + want: defaultNextRunIn, + }, + { + name: "1-hour-revoked-credential", + args: []args{ + {s: RevokedCredential, e: time.Hour}, + }, + want: defaultNextRunIn, + }, + { + name: "1-hour-expired-credential", + args: []args{ + {s: ExpiredCredential, e: time.Hour}, + }, + want: defaultNextRunIn, + }, + { + name: "multiple-all-active", + args: []args{ + {s: ActiveCredential, e: 24 * time.Hour}, + {s: ActiveCredential, e: 6 * time.Hour}, + {s: ActiveCredential, e: 10 * time.Hour}, + }, + // 6 hours is the soonest expiration time + want: 3 * time.Hour, + }, + { + name: "multiple-mixed", + args: []args{ + {s: RevokeCredential, e: 2 * time.Hour}, + {s: ActiveCredential, e: 48 * time.Hour}, + {s: ActiveCredential, e: 24 * time.Hour}, + {s: RevokedCredential, e: 6 * time.Hour}, + {s: RevokeCredential, e: 8 * time.Hour}, + {s: ExpiredCredential, e: 10 * time.Hour}, + }, + // 24 hours is the soonest active expiration time + want: 12 * time.Hour, + }, + { + name: "overdue-active-renewal", + args: []args{ + {s: ActiveCredential, e: -12 * time.Hour}, + }, + want: 0, + }, + { + name: "non-active-overdue", + args: []args{ + {s: ActiveCredential, e: 24 * time.Hour}, + {s: ExpiredCredential, e: -12 * time.Hour}, + {s: RevokeCredential, e: -12 * time.Hour}, + {s: RevokedCredential, e: -12 * time.Hour}, + }, + // The active credential is expiring in 24 hours the non active credentials + // that are overdue should not impact the query + want: 12 * time.Hour, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + r, err := newCredentialRenewalJob(rw, rw, kmsCache, hclog.L()) + assert.NoError(err) + require.NotNil(r) + + token := testVaultToken(t, conn, wrapper, v, cs, CurrentToken, 5*time.Minute) + for _, cred := range tt.args { + testVaultCred(t, conn, v, cl, sess, token, cred.s, cred.e) + } + + got, err := r.NextRunIn() + require.NoError(err) + // Round to time.Minute to account for lost time between creating credentials and determining next run + assert.Equal(tt.want.Round(time.Minute), got.Round(time.Minute)) + + // Set all credentials to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_credential set status = 'revoked'", nil) + assert.NoError(err) + }) + } +} + +func TestNewCredentialRevocationJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + logger hclog.Logger + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil logger", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newCredentialRevocationJob(tt.args.r, tt.args.w, tt.args.kms, tt.args.logger, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + require.NotNil(got.logger) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestCredentialRevocationJob_RunLimits(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + _, ct := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(t, err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(t, err) + cs, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(t, err) + + libPath := path.Join("database", "creds", "opened") + cl, err := NewCredentialLibrary(cs.PublicId, libPath, WithMethod(MethodGet)) + require.NoError(t, err) + cl.PublicId, err = newCredentialLibraryId() + require.NoError(t, err) + err = rw.Create(context.Background(), cl) + require.NoError(t, err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + repoToken := allocToken() + require.NoError(t, rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + count := 10 + tests := []struct { + name string + opts []Option + wantLen int + }{ + { + name: "with-no-limits", + wantLen: count, + }, + { + name: "with-negative-limit", + opts: []Option{WithLimit(-1)}, + wantLen: count, + }, + { + name: "with-limit", + opts: []Option{WithLimit(2)}, + wantLen: 2, + }, + { + name: "with-limit-greater-than-count", + opts: []Option{WithLimit(count + 5)}, + wantLen: count, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + for i := 0; i < count*4; i++ { + var status CredentialStatus + // Alternative between cred status, active, revoked and expired should have + // no impact on number creds revoked. Only revoke creds should be revoked. + switch i % 4 { + case 0: + status = ActiveCredential + case 1: + status = RevokeCredential + case 2: + status = RevokedCredential + case 3: + status = ExpiredCredential + } + testVaultCred(t, conn, v, cl, sess, repoToken, status, 5*time.Minute) + } + + r, err := newCredentialRevocationJob(rw, rw, kmsCache, hclog.L(), tt.opts...) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(tt.wantLen, r.numCreds) + + // Set all credentials to revoked for next test + _, err = rw.Exec(context.Background(), "update credential_vault_credential set status = 'revoked'", nil) + assert.NoError(err) + }) + } +} + +func TestCredentialRevocationJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + testDb := v.MountDatabase(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn) + require.NoError(err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + r, err := newCredentialRevocationJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = r.Run(context.Background()) + require.NoError(err) + // No credentials should have been revoked + assert.Equal(0, r.numCreds) + + secret1, _ := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, 5*time.Minute) + revokeSecret, revokeCred := testVaultCred(t, conn, v, cl, sess, repoToken, RevokeCredential, 5*time.Minute) + secret2, _ := testVaultCred(t, conn, v, cl, sess, repoToken, RevokedCredential, 5*time.Minute) + secret3, _ := testVaultCred(t, conn, v, cl, sess, repoToken, ExpiredCredential, 5*time.Minute) + + // Verify the revokeCred has a status of revoke + lookupCred := allocCredential() + lookupCred.PublicId = revokeCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(RevokeCredential), lookupCred.Status) + + // Verify revokeCred is valid in testDb + assert.NoError(testDb.ValidateCredential(t, revokeSecret)) + + err = r.Run(context.Background()) + require.NoError(err) + // The revoke credential should have been revoked + assert.Equal(1, r.numCreds) + + // revokeCred should now have a status of revoked + lookupCred = allocCredential() + lookupCred.PublicId = revokeCred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(RevokedCredential), lookupCred.Status) + + // revokeCred should no longer be valid in test database + assert.Error(testDb.ValidateCredential(t, revokeSecret)) + + // Other creds should still be valid in test database + assert.NoError(testDb.ValidateCredential(t, secret1)) + assert.NoError(testDb.ValidateCredential(t, secret2)) + assert.NoError(testDb.ValidateCredential(t, secret3)) +} + +func TestCredentialRevocationJob_RunDeleted(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + testDb := v.MountDatabase(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn) + require.NoError(err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + r, err := newCredentialRevocationJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + secret, cred := testVaultCred(t, conn, v, cl, sess, repoToken, ActiveCredential, 5*time.Hour) + + err = r.Run(context.Background()) + require.NoError(err) + // No credentials should have been revoked as expiration is 5 hours from now + assert.Equal(0, r.numCreds) + + // Deleting the library should set the cred library_id to null, but not revoke the cred + count, err := rw.Delete(context.Background(), cl) + require.NoError(err) + assert.Equal(1, count) + + err = r.Run(context.Background()) + require.NoError(err) + // No credentials should have been revoked + assert.Equal(0, r.numCreds) + + // Verify the cred has a status of active with an empty libraryId + lookupCred := allocCredential() + lookupCred.PublicId = cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(ActiveCredential), lookupCred.Status) + assert.Empty(lookupCred.LibraryId) + + // secret should still be valid in test database + assert.NoError(testDb.ValidateCredential(t, secret)) + + // Deleting the session should set the cred session_id to null and schedule cred for revocation + count, err = rw.Delete(context.Background(), sess) + require.NoError(err) + assert.Equal(1, count) + + // cred should now have a status of revoke and empty sessionId + lookupCred = allocCredential() + lookupCred.PublicId = cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Empty(lookupCred.SessionId) + assert.Equal(string(RevokeCredential), lookupCred.Status) + + err = r.Run(context.Background()) + require.NoError(err) + // The revoke credential should have been revoked + assert.Equal(1, r.numCreds) + + // cred should now have a status of revoked + lookupCred = allocCredential() + lookupCred.PublicId = cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(RevokedCredential), lookupCred.Status) + + // secret should no longer be valid in test database + assert.Error(testDb.ValidateCredential(t, secret)) +} + +func TestNewCredentialStoreCleanupJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + logger hclog.Logger + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil reader", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil writer", + args: args{ + r: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil kms", + args: args{ + r: rw, + w: rw, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "nil logger", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-no-options", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + wantLimit: db.DefaultLimit, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + logger: hclog.L(), + }, + options: []Option{WithLimit(100)}, + wantLimit: 100, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newCredentialStoreCleanupJob(tt.args.r, tt.args.w, tt.args.kms, tt.args.logger, tt.options...) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.r, got.reader) + assert.Equal(tt.args.w, got.writer) + assert.Equal(tt.args.kms, got.kms) + require.NotNil(got.logger) + assert.Equal(tt.wantLimit, got.limit) + }) + } +} + +func TestCredentialStoreCleanupJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + v := NewTestVaultServer(t) + + _, ct := v.CreateToken(t) + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(err) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + cs1, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + _, ct = v.CreateToken(t) + in, err = NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(ct)) + require.NoError(err) + cs2, err := repo.CreateCredentialStore(context.Background(), in) + require.NoError(err) + + // Get token hmac for verifications below + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "store_id = ?", []interface{}{cs1.PublicId})) + cs1TokenHmac := repoToken.TokenHmac + + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "store_id = ?", []interface{}{cs2.PublicId})) + cs2TokenHmac := repoToken.TokenHmac + + // create second token on cs2 + secondToken := testVaultToken(t, conn, wrapper, v, cs2, MaintainingToken, time.Hour) + + r, err := newCredentialStoreCleanupJob(rw, rw, kmsCache, hclog.L()) + require.NoError(err) + + err = sche.RegisterJob(context.Background(), r) + require.NoError(err) + + // No credential stores should have been cleaned up + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numStores) + + // Soft delete both credential stores + count, err := repo.DeleteCredentialStore(context.Background(), cs1.PublicId) + require.NoError(err) + assert.Equal(1, count) + + count, err = repo.DeleteCredentialStore(context.Background(), cs2.PublicId) + require.NoError(err) + assert.Equal(1, count) + + // Verify tokens have been set to revoke + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "store_id = ?", []interface{}{cs1.PublicId})) + assert.Equal(string(RevokeToken), repoToken.Status) + + repoToken = allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "store_id = ?", []interface{}{cs2.PublicId})) + assert.Equal(string(RevokeToken), repoToken.Status) + + // Both soft deleted credential stores should not be cleaned up yet + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numStores) + + // Update cs1 token to be marked as revoked + count, err = rw.Exec(context.Background(), updateTokenStatusQuery, []interface{}{RevokedToken, cs1TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + + // cs1 should be deleted + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(1, r.numStores) + + // Lookup of cs1 and its token should fail + agg := allocPublicStore() + agg.PublicId = cs1.PublicId + err = rw.LookupByPublicId(context.Background(), agg) + require.Error(err) + assert.True(errors.IsNotFoundError(err)) + repoToken = allocToken() + err = rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs1TokenHmac}) + require.Error(err) + assert.True(errors.IsNotFoundError(err)) + + // Lookup of cs2 and its token should not error + _, err = repo.LookupCredentialStore(context.Background(), cs2.PublicId) + require.NoError(err) + repoToken = allocToken() + err = rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs2TokenHmac}) + require.NoError(err) + + // Update cs2 token expiration time + count, err = rw.Exec(context.Background(), "update credential_vault_token set expiration_time = now() where token_hmac = ?;", []interface{}{cs2TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + + // cs2 still has a second token not yet revoked/expired + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numStores) + + // Lookup of cs2 and its token should not error + _, err = repo.LookupCredentialStore(context.Background(), cs2.PublicId) + require.NoError(err) + repoToken = allocToken() + err = rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs2TokenHmac}) + require.NoError(err) + + // set secondToken with an expired status + count, err = rw.Exec(context.Background(), updateTokenStatusQuery, []interface{}{ExpiredToken, secondToken.TokenHmac}) + require.NoError(err) + assert.Equal(1, count) + + // With no un-expired or un-revoked tokens cs2 should now be deleted + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(1, r.numStores) + + // Lookup of cs2 and its token should fail + agg = allocPublicStore() + agg.PublicId = cs2.PublicId + err = rw.LookupByPublicId(context.Background(), agg) + require.Error(err) + assert.True(errors.IsNotFoundError(err)) + repoToken = allocToken() + err = rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs2TokenHmac}) + require.Error(err) + assert.True(errors.IsNotFoundError(err)) + err = rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{secondToken.TokenHmac}) + require.Error(err) + assert.True(errors.IsNotFoundError(err)) +} + +func TestNewCredentialCleanupJob(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + + type args struct { + w db.Writer + } + tests := []struct { + name string + args args + options []Option + wantLimit int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "nil writer", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid", + args: args{ + w: rw, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + got, err := newCredentialCleanupJob(tt.args.w) + if tt.wantErr { + require.Error(err) + assert.Nil(got) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + require.NotNil(got) + assert.Equal(tt.args.w, got.writer) + }) + } +} + +func TestCredentialCleanupJob_Run(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kmsCache, sche) + require.NoError(err) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token)) + require.NoError(err) + cs, err := repo.CreateCredentialStore(context.Background(), credStoreIn) + require.NoError(err) + + libPath := path.Join("database", "creds", "opened") + libIn, err := NewCredentialLibrary(cs.GetPublicId(), libPath) + require.NoError(err) + cl, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libIn) + require.NoError(err) + + at := authtoken.TestAuthToken(t, conn, kmsCache, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + sess1 := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + sess2 := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + repoToken := allocToken() + require.NoError(rw.LookupWhere(context.Background(), &repoToken, "token_hmac = ?", []interface{}{cs.outputToken.TokenHmac})) + + r, err := newCredentialCleanupJob(rw) + require.NoError(err) + + _, sess1Cred1 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour) + _, sess1Cred2 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour) + _, sess1Cred3 := testVaultCred(t, conn, v, cl, sess1, repoToken, ActiveCredential, 5*time.Hour) + _, sess2Cred := testVaultCred(t, conn, v, cl, sess2, repoToken, ActiveCredential, 5*time.Hour) + + // No credentials should be cleaned up + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numCreds) + + // Delete sess1 + count, err := rw.Delete(context.Background(), sess1) + require.NoError(err) + assert.Equal(1, count) + + // Credentials are still in the revoke state so none should be deleted yet + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(0, r.numCreds) + + query, queryArgs := sess1Cred1.updateStatusQuery(RevokedCredential) + count, err = rw.Exec(context.Background(), query, queryArgs) + require.NoError(err) + assert.Equal(1, count) + + query, queryArgs = sess1Cred2.updateStatusQuery(ExpiredCredential) + count, err = rw.Exec(context.Background(), query, queryArgs) + require.NoError(err) + assert.Equal(1, count) + + query, queryArgs = sess1Cred3.updateStatusQuery(UnknownCredentialStatus) + count, err = rw.Exec(context.Background(), query, queryArgs) + require.NoError(err) + assert.Equal(1, count) + + query, queryArgs = sess2Cred.updateStatusQuery(RevokedCredential) + count, err = rw.Exec(context.Background(), query, queryArgs) + require.NoError(err) + assert.Equal(1, count) + + // Only the three credentials associated with the deleted session should be deleted + err = r.Run(context.Background()) + require.NoError(err) + assert.Equal(3, r.numCreds) + + // Session 1 creds should no longer exist + lookupCred := allocCredential() + lookupCred.PublicId = sess1Cred1.PublicId + require.Error(rw.LookupById(context.Background(), lookupCred)) + lookupCred.PublicId = sess1Cred2.PublicId + require.Error(rw.LookupById(context.Background(), lookupCred)) + lookupCred.PublicId = sess1Cred3.PublicId + require.Error(rw.LookupById(context.Background(), lookupCred)) + + // Session 2 creds should still exist but be revoked + lookupCred.PublicId = sess2Cred.PublicId + require.NoError(rw.LookupById(context.Background(), lookupCred)) + assert.Equal(string(RevokedCredential), lookupCred.Status) +} diff --git a/internal/credential/vault/options.go b/internal/credential/vault/options.go new file mode 100644 index 000000000..143cbe621 --- /dev/null +++ b/internal/credential/vault/options.go @@ -0,0 +1,111 @@ +package vault + +// getOpts - iterate the inbound Options and return a struct +func getOpts(opt ...Option) options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments. +type Option func(*options) + +// options = how options are represented +type options struct { + withName string + withDescription string + withLimit int + withCACert []byte + withNamespace string + withTlsServerName string + withTlsSkipVerify bool + withClientCert *ClientCertificate + withMethod Method + withRequestBody []byte +} + +func getDefaultOptions() options { + return options{} +} + +// WithDescription provides an optional description. +func WithDescription(desc string) Option { + return func(o *options) { + o.withDescription = desc + } +} + +// WithName provides an optional name. +func WithName(name string) Option { + return func(o *options) { + o.withName = name + } +} + +// WithLimit provides an option to provide a limit. Intentionally allowing +// negative integers. If WithLimit < 0, then unlimited results are +// returned. If WithLimit == 0, then default limits are used for results. +func WithLimit(l int) Option { + return func(o *options) { + o.withLimit = l + } +} + +// WithCACert provides an optional PEM-encoded certificate +// to verify the Vault server's SSL certificate. +func WithCACert(cert []byte) Option { + return func(o *options) { + o.withCACert = cert + } +} + +// WithNamespace provides an optional Vault namespace. +func WithNamespace(namespace string) Option { + return func(o *options) { + o.withNamespace = namespace + } +} + +// WithTlsServerName provides an optional name to use as the SNI host when +// connecting to Vault via TLS. +func WithTlsServerName(name string) Option { + return func(o *options) { + o.withTlsServerName = name + } +} + +// WithTlsSkipVerify provides an option to disable verification of TLS +// certificates when connection to Vault. Using this option is highly +// discouraged as it decreases the security of data transmissions to and +// from the Vault server. +func WithTlsSkipVerify(skipVerify bool) Option { + return func(o *options) { + o.withTlsSkipVerify = skipVerify + } +} + +// WithClientCert provides an optional ClientCertificate to use for TLS +// authentication to a Vault server. +func WithClientCert(clientCert *ClientCertificate) Option { + return func(o *options) { + o.withClientCert = clientCert + } +} + +// WithMethod provides an optional Method to use for communicating with +// Vault. +func WithMethod(m Method) Option { + return func(o *options) { + o.withMethod = m + } +} + +// WithRequestBody provides an optional request body for sending to Vault +// when requesting credentials using HTTP Post. +func WithRequestBody(b []byte) Option { + return func(o *options) { + o.withRequestBody = b + } +} diff --git a/internal/credential/vault/options_test.go b/internal/credential/vault/options_test.go new file mode 100644 index 000000000..b4f780109 --- /dev/null +++ b/internal/credential/vault/options_test.go @@ -0,0 +1,86 @@ +package vault + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_GetOpts(t *testing.T) { + t.Parallel() + t.Run("WithName", func(t *testing.T) { + opts := getOpts(WithName("test")) + testOpts := getDefaultOptions() + testOpts.withName = "test" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithDescription", func(t *testing.T) { + opts := getOpts(WithDescription("test desc")) + testOpts := getDefaultOptions() + testOpts.withDescription = "test desc" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithLimit", func(t *testing.T) { + opts := getOpts(WithLimit(5)) + testOpts := getDefaultOptions() + testOpts.withLimit = 5 + assert.Equal(t, opts, testOpts) + }) + t.Run("WithCACert", func(t *testing.T) { + opts := getOpts(WithCACert([]byte("test cert"))) + testOpts := getDefaultOptions() + testOpts.withCACert = []byte("test cert") + assert.Equal(t, opts, testOpts) + }) + t.Run("WithNamespace", func(t *testing.T) { + opts := getOpts(WithNamespace("namespace")) + testOpts := getDefaultOptions() + testOpts.withNamespace = "namespace" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithTlsServerName", func(t *testing.T) { + opts := getOpts(WithTlsServerName("server")) + testOpts := getDefaultOptions() + testOpts.withTlsServerName = "server" + assert.Equal(t, opts, testOpts) + }) + t.Run("WithTlsSkipVerify", func(t *testing.T) { + opts := getOpts(WithTlsSkipVerify(true)) + testOpts := getDefaultOptions() + testOpts.withTlsSkipVerify = true + assert.Equal(t, opts, testOpts) + }) + t.Run("WithClientCert", func(t *testing.T) { + testOpts := getDefaultOptions() + assert.Nil(t, testOpts.withClientCert) + inCert := testClientCert(t, testCaCert(t)) + cert := inCert.Cert.Cert + key := inCert.Cert.Key + clientCert, err := NewClientCertificate(cert, key) + assert.NoError(t, err) + assert.NotNil(t, clientCert) + opts := getOpts(WithClientCert(clientCert)) + require.NotNil(t, opts.withClientCert) + assert.Equal(t, cert, opts.withClientCert.Certificate) + assert.Equal(t, key, opts.withClientCert.CertificateKey) + }) + t.Run("WithMethod_Get", func(t *testing.T) { + opts := getOpts(WithMethod(MethodGet)) + testOpts := getDefaultOptions() + testOpts.withMethod = MethodGet + assert.Equal(t, opts, testOpts) + }) + t.Run("WithMethod_Post", func(t *testing.T) { + opts := getOpts(WithMethod(MethodPost)) + testOpts := getDefaultOptions() + testOpts.withMethod = MethodPost + assert.Equal(t, opts, testOpts) + }) + t.Run("WithRequestBody", func(t *testing.T) { + opts := getOpts(WithRequestBody([]byte("body"))) + testOpts := getDefaultOptions() + testOpts.withRequestBody = []byte("body") + assert.Equal(t, opts, testOpts) + }) +} diff --git a/internal/credential/vault/private_credential.go b/internal/credential/vault/private_credential.go new file mode 100644 index 000000000..69a75733f --- /dev/null +++ b/internal/credential/vault/private_credential.go @@ -0,0 +1,131 @@ +package vault + +import ( + "context" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" + "google.golang.org/protobuf/proto" +) + +type privateCredential struct { + PublicId string `gorm:"primary_key"` + LibraryId string + SessionId string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + ExternalId string + LastRenewalTime *timestamp.Timestamp + ExpirationTime *timestamp.Timestamp + IsRenewable bool + Status string + RenewalTime *timestamp.Timestamp + TokenHmac []byte + Token TokenSecret + CtToken []byte + TokenCreateTime *timestamp.Timestamp + TokenUpdateTime *timestamp.Timestamp + TokenLastRenewalTime *timestamp.Timestamp + TokenExpirationTime *timestamp.Timestamp + TokenKeyId string + TokenStatus string + ScopeId string + VaultAddress string + Namespace string + CaCert []byte + TlsServerName string + TlsSkipVerify bool + ClientCert []byte + ClientKey KeySecret + CtClientKey []byte + ClientKeyHmac []byte + ClientKeyId string +} + +func (pc *privateCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(privateCredential).decrypt" + if pc.CtToken != nil { + type ptk struct { + Token []byte `wrapping:"pt,token_data"` + CtToken []byte `wrapping:"ct,token_data"` + } + ptkv := &ptk{ + CtToken: pc.CtToken, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token")) + } + pc.Token = ptkv.Token + } + + if pc.CtClientKey != nil && pc.ClientCert != nil { + type pck struct { + Key []byte `wrapping:"pt,key_data"` + CtKey []byte `wrapping:"ct,key_data"` + } + pckv := &pck{ + CtKey: pc.CtClientKey, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate")) + } + pc.ClientKey = pckv.Key + } + return nil +} + +func (pc *privateCredential) client() (*client, error) { + const op = "vault.(privateCredential).client" + clientConfig := &clientConfig{ + Addr: pc.VaultAddress, + Token: pc.Token, + CaCert: pc.CaCert, + TlsServerName: pc.TlsServerName, + TlsSkipVerify: pc.TlsSkipVerify, + Namespace: pc.Namespace, + } + + if pc.ClientKey != nil { + clientConfig.ClientCert = pc.ClientCert + clientConfig.ClientKey = pc.ClientKey + } + + client, err := newClient(clientConfig) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client")) + } + return client, nil +} + +func (pc *privateCredential) toCredential() *Credential { + return &Credential{ + Credential: &store.Credential{ + PublicId: pc.PublicId, + LibraryId: pc.LibraryId, + SessionId: pc.SessionId, + TokenHmac: append(pc.TokenHmac[:0:0], pc.TokenHmac...), + CreateTime: proto.Clone(pc.CreateTime).(*timestamp.Timestamp), + UpdateTime: proto.Clone(pc.UpdateTime).(*timestamp.Timestamp), + Version: pc.Version, + ExternalId: pc.ExternalId, + LastRenewalTime: proto.Clone(pc.LastRenewalTime).(*timestamp.Timestamp), + ExpirationTime: proto.Clone(pc.ExpirationTime).(*timestamp.Timestamp), + IsRenewable: pc.IsRenewable, + Status: pc.Status, + }, + } +} + +// GetPublicId returns the public id. +func (pc *privateCredential) GetPublicId() string { + return pc.PublicId +} + +// TableName returns the table name for gorm. +func (pc *privateCredential) TableName() string { + return "credential_vault_credential_private" +} diff --git a/internal/credential/vault/private_library.go b/internal/credential/vault/private_library.go new file mode 100644 index 000000000..6e8cc2f00 --- /dev/null +++ b/internal/credential/vault/private_library.go @@ -0,0 +1,260 @@ +package vault + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" + "google.golang.org/protobuf/proto" +) + +var _ credential.Dynamic = (*actualCredential)(nil) + +type actualCredential struct { + id string + sessionId string + lib *privateLibrary + secretData map[string]interface{} + purpose credential.Purpose +} + +func (ac *actualCredential) GetPublicId() string { return ac.id } +func (ac *actualCredential) GetSessionId() string { return ac.sessionId } +func (ac *actualCredential) Secret() credential.SecretData { return ac.secretData } +func (ac *actualCredential) Library() credential.Library { return ac.lib } +func (ac *actualCredential) Purpose() credential.Purpose { return ac.purpose } + +var _ credential.Library = (*privateLibrary)(nil) + +type privateLibrary struct { + PublicId string `gorm:"primary_key"` + StoreId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + ScopeId string + VaultPath string + HttpMethod string + HttpRequestBody []byte + VaultAddress string + Namespace string + CaCert []byte + TlsServerName string + TlsSkipVerify bool + TokenHmac []byte + Token TokenSecret + CtToken []byte + TokenKeyId string + ClientCert []byte + ClientKey KeySecret + CtClientKey []byte + ClientKeyId string + Purpose credential.Purpose `gorm:"-"` +} + +func (pl *privateLibrary) clone() *privateLibrary { + // The 'append(a[:0:0], a...)' comes from + // https://github.com/go101/go101/wiki/How-to-perfectly-clone-a-slice%3F + return &privateLibrary{ + PublicId: pl.PublicId, + StoreId: pl.StoreId, + Name: pl.Name, + Description: pl.Description, + CreateTime: proto.Clone(pl.CreateTime).(*timestamp.Timestamp), + UpdateTime: proto.Clone(pl.UpdateTime).(*timestamp.Timestamp), + Version: pl.Version, + ScopeId: pl.ScopeId, + VaultPath: pl.VaultPath, + HttpMethod: pl.HttpMethod, + HttpRequestBody: append(pl.HttpRequestBody[:0:0], pl.HttpRequestBody...), + VaultAddress: pl.VaultAddress, + Namespace: pl.Namespace, + CaCert: append(pl.CaCert[:0:0], pl.CaCert...), + TlsServerName: pl.TlsServerName, + TlsSkipVerify: pl.TlsSkipVerify, + TokenHmac: append(pl.TokenHmac[:0:0], pl.TokenHmac...), + Token: append(pl.Token[:0:0], pl.Token...), + CtToken: append(pl.CtToken[:0:0], pl.CtToken...), + TokenKeyId: pl.TokenKeyId, + ClientCert: append(pl.ClientCert[:0:0], pl.ClientCert...), + ClientKey: append(pl.ClientKey[:0:0], pl.ClientKey...), + CtClientKey: append(pl.CtClientKey[:0:0], pl.CtClientKey...), + ClientKeyId: pl.ClientKeyId, + Purpose: pl.Purpose, + } +} + +func (pl *privateLibrary) GetPublicId() string { return pl.PublicId } +func (pl *privateLibrary) GetStoreId() string { return pl.StoreId } +func (pl *privateLibrary) GetName() string { return pl.Name } +func (pl *privateLibrary) GetDescription() string { return pl.Description } +func (pl *privateLibrary) GetVersion() uint32 { return pl.Version } +func (pl *privateLibrary) GetCreateTime() *timestamp.Timestamp { return pl.CreateTime } +func (pl *privateLibrary) GetUpdateTime() *timestamp.Timestamp { return pl.UpdateTime } + +func (pl *privateLibrary) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(privateLibrary).decrypt" + + if pl.CtToken != nil { + type ptk struct { + Token []byte `wrapping:"pt,token_data"` + CtToken []byte `wrapping:"ct,token_data"` + } + ptkv := &ptk{ + CtToken: pl.CtToken, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token")) + } + pl.Token = ptkv.Token + } + + if pl.CtClientKey != nil && pl.ClientCert != nil { + type pck struct { + Key []byte `wrapping:"pt,key_data"` + CtKey []byte `wrapping:"ct,key_data"` + } + pckv := &pck{ + CtKey: pl.CtClientKey, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate")) + } + pl.ClientKey = pckv.Key + } + return nil +} + +func (pl *privateLibrary) client() (*client, error) { + const op = "vault.(privateLibrary).client" + clientConfig := &clientConfig{ + Addr: pl.VaultAddress, + Token: pl.Token, + CaCert: pl.CaCert, + TlsServerName: pl.TlsServerName, + TlsSkipVerify: pl.TlsSkipVerify, + Namespace: pl.Namespace, + } + + if pl.ClientKey != nil { + clientConfig.ClientCert = pl.ClientCert + clientConfig.ClientKey = pl.ClientKey + } + + client, err := newClient(clientConfig) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client")) + } + return client, nil +} + +// TableName returns the table name for gorm. +func (pl *privateLibrary) TableName() string { + return "credential_vault_library_private" +} + +func (r *Repository) getPrivateLibraries(ctx context.Context, requests []credential.Request) ([]*privateLibrary, error) { + const op = "vault.(Repository).getPrivateLibraries" + + mapper, err := newMapper(requests) + if err != nil { + return nil, errors.Wrap(err, op) + } + + libIds := mapper.libIds() + + inClause := strings.TrimSuffix(strings.Repeat("?,", len(libIds)), ",") + + query := fmt.Sprintf(selectPrivateLibrariesQuery, inClause) + + var params []interface{} + for _, v := range libIds { + params = append(params, v) + } + rows, err := r.reader.Query(ctx, query, params) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("query failed")) + } + defer rows.Close() + + var libs []*privateLibrary + for rows.Next() { + var lib privateLibrary + if err := r.reader.ScanRows(rows, &lib); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed")) + } + purps := mapper.get(lib.GetPublicId()) + if len(purps) == 0 { + return nil, errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("unknown library")) + } + for _, purp := range purps { + cp := lib.clone() + cp.Purpose = purp + libs = append(libs, cp) + } + } + + for _, pl := range libs { + databaseWrapper, err := r.kms.GetWrapper(ctx, pl.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + + if err := pl.decrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(err, op) + } + } + + return libs, nil +} + +// requestMap takes a slice of credential requests and provides of list of +// library IDs with duplicate IDs removed. It also provides a way to lookup +// the list of credential purposes for a particular library ID. +// +// A single library can be used to retrieve multiple credentials as long as +// each credential is for a different purpose. When retrieving the private +// libraries from the database, a list of library IDs with duplicates +// removed is needed. When requesting credentials from vault, any library +// being used for multiple purposes needs to be duplicated with the purpose +// so multiple requests are made to vault using the same library. +type requestMap struct { + ids map[string][]credential.Purpose +} + +func newMapper(requests []credential.Request) (*requestMap, error) { + ids := make(map[string][]credential.Purpose, len(requests)) + for _, req := range requests { + if purps, ok := ids[req.SourceId]; ok { + for _, purp := range purps { + if purp == req.Purpose { + return nil, errors.E(errors.WithCode(errors.InvalidParameter), errors.WithMsg("duplicate library and purpose")) + } + } + } + ids[req.SourceId] = append(ids[req.SourceId], req.Purpose) + } + return &requestMap{ + ids: ids, + }, nil +} + +func (m *requestMap) libIds() []string { + var ids []string + for id := range m.ids { + ids = append(ids, id) + } + return ids +} + +func (m *requestMap) get(libraryId string) []credential.Purpose { + return m.ids[libraryId] +} diff --git a/internal/credential/vault/private_library_test.go b/internal/credential/vault/private_library_test.go new file mode 100644 index 000000000..7fd2b787c --- /dev/null +++ b/internal/credential/vault/private_library_test.go @@ -0,0 +1,227 @@ +package vault + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_getPrivateLibraries(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + tests := []struct { + name string + tls TestVaultTLS + }{ + { + name: "no-tls-valid-token", + }, + { + name: "server-tls-valid-token", + tls: TestServerTLS, + }, + { + name: "client-tls-valid-token", + tls: TestClientTLS, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls)) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kms := kms.TestKms(t, conn, wrapper) + + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + + var opts []Option + if tt.tls == TestServerTLS { + opts = append(opts, WithCACert(v.CaCert)) + } + if tt.tls == TestClientTLS { + opts = append(opts, WithCACert(v.CaCert)) + clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(err) + opts = append(opts, WithClientCert(clientCert)) + } + + _, token := v.CreateToken(t) + + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...) + assert.NoError(err) + require.NotNil(credStoreIn) + origStore, err := repo.CreateCredentialStore(ctx, credStoreIn) + assert.NoError(err) + require.NotNil(origStore) + + origLookup, err := repo.LookupCredentialStore(ctx, origStore.GetPublicId()) + assert.NoError(err) + require.NotNil(origLookup) + assert.NotNil(origLookup.Token()) + assert.Equal(origStore.GetPublicId(), origLookup.GetPublicId()) + + libs := make(map[string]*CredentialLibrary, 3) + var requests []credential.Request + { + libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path") + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[lib.GetPublicId()] = lib + req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose} + requests = append(requests, req) + } + { + libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", WithMethod(MethodPost)) + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[lib.GetPublicId()] = lib + req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose} + requests = append(requests, req) + } + { + libIn, err := NewCredentialLibrary(origStore.GetPublicId(), "/vault/path", WithMethod(MethodPost), WithRequestBody([]byte(`{"common_name":"boundary.com"}`))) + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[lib.GetPublicId()] = lib + req := credential.Request{SourceId: lib.GetPublicId(), Purpose: credential.ApplicationPurpose} + requests = append(requests, req) + } + + gotLibs, err := repo.getPrivateLibraries(ctx, requests) + assert.NoError(err) + require.NotNil(gotLibs) + assert.Len(gotLibs, len(libs)) + + for _, got := range gotLibs { + assert.Equal(TokenSecret(token), got.Token) + if tt.tls == TestClientTLS { + require.NotNil(got.ClientKey) + assert.Equal(v.ClientKey, []byte(got.ClientKey)) + } + want, ok := libs[got.PublicId] + require.True(ok) + assert.Equal(prj.GetPublicId(), got.ScopeId) + assert.Equal(want.StoreId, got.StoreId) + assert.Equal(want.VaultPath, got.VaultPath) + assert.Equal(want.HttpMethod, got.HttpMethod) + assert.Equal(want.HttpRequestBody, got.HttpRequestBody) + } + }) + } +} + +func TestRequestMap(t *testing.T) { + type args struct { + requests []credential.Request + } + + tests := []struct { + name string + args args + wantLibIds []string + wantErr bool + }{ + { + name: "empty", + }, + { + name: "one", + args: args{ + requests: []credential.Request{ + { + SourceId: "kaz", + Purpose: credential.ApplicationPurpose, + }, + }, + }, + wantLibIds: []string{"kaz"}, + }, + { + name: "two-libs", + args: args{ + requests: []credential.Request{ + { + SourceId: "kaz", + Purpose: credential.ApplicationPurpose, + }, + { + SourceId: "gary", + Purpose: credential.EgressPurpose, + }, + }, + }, + wantLibIds: []string{"kaz", "gary"}, + }, + { + name: "one-lib-two-purps", + args: args{ + requests: []credential.Request{ + { + SourceId: "kaz", + Purpose: credential.ApplicationPurpose, + }, + { + SourceId: "kaz", + Purpose: credential.EgressPurpose, + }, + }, + }, + wantLibIds: []string{"kaz"}, + }, + { + name: "one-lib-dup-purps-error", + args: args{ + requests: []credential.Request{ + { + SourceId: "kaz", + Purpose: credential.ApplicationPurpose, + }, + { + SourceId: "kaz", + Purpose: credential.ApplicationPurpose, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + mapper, err := newMapper(tt.args.requests) + if tt.wantErr { + assert.Error(err) + assert.Nil(mapper) + return + } + assert.NoError(err) + require.NotNil(mapper) + assert.ElementsMatch(tt.wantLibIds, mapper.libIds()) + }) + } +} diff --git a/internal/credential/vault/private_store.go b/internal/credential/vault/private_store.go new file mode 100644 index 000000000..4bd472ad0 --- /dev/null +++ b/internal/credential/vault/private_store.go @@ -0,0 +1,212 @@ +package vault + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" +) + +func (r *Repository) listRevokePrivateStores(ctx context.Context, opt ...Option) ([]*privateStore, error) { + const op = "vault.(Repository).listRevokePrivateStores" + + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + limit = opts.withLimit + } + + var stores []*privateStore + where, values := "token_status = ?", []interface{}{"revoke"} + if err := r.reader.SearchWhere(ctx, &stores, where, values, db.WithLimit(limit)); err != nil { + return nil, errors.Wrap(err, op) + } + + for _, store := range stores { + databaseWrapper, err := r.kms.GetWrapper(ctx, store.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + if err := store.decrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(err, op) + } + } + return stores, nil +} + +func (r *Repository) lookupPrivateStore(ctx context.Context, publicId string) (*privateStore, error) { + const op = "vault.(Repository).lookupPrivateStore" + if publicId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no public id") + } + ps := allocPrivateStore() + if err := r.reader.LookupWhere(ctx, &ps, "public_id = ? and token_status = ?", publicId, CurrentToken); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) + } + + databaseWrapper, err := r.kms.GetWrapper(ctx, ps.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + + if err := ps.decrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(err, op) + } + + return ps, nil +} + +type privateStore struct { + PublicId string `gorm:"primary_key"` + ScopeId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + DeleteTime *timestamp.Timestamp + Version uint32 + VaultAddress string + Namespace string + CaCert []byte + TlsServerName string + TlsSkipVerify bool + StoreId string + TokenHmac []byte + Token TokenSecret + CtToken []byte + TokenCreateTime *timestamp.Timestamp + TokenUpdateTime *timestamp.Timestamp + TokenLastRenewalTime *timestamp.Timestamp + TokenExpirationTime *timestamp.Timestamp + TokenRenewalTime *timestamp.Timestamp + TokenKeyId string + TokenStatus string + ClientCert []byte + ClientKeyId string + ClientKey KeySecret + CtClientKey []byte + ClientCertKeyHmac []byte +} + +func allocPrivateStore() *privateStore { + return &privateStore{} +} + +func (ps *privateStore) toCredentialStore() *CredentialStore { + cs := allocCredentialStore() + cs.PublicId = ps.PublicId + cs.ScopeId = ps.ScopeId + cs.Name = ps.Name + cs.Description = ps.Description + cs.CreateTime = ps.CreateTime + cs.UpdateTime = ps.UpdateTime + cs.DeleteTime = ps.DeleteTime + cs.Version = ps.Version + cs.VaultAddress = ps.VaultAddress + cs.Namespace = ps.Namespace + cs.CaCert = ps.CaCert + cs.TlsServerName = ps.TlsServerName + cs.TlsSkipVerify = ps.TlsSkipVerify + cs.privateToken = ps.token() + if ps.ClientCert != nil { + cert := allocClientCertificate() + cert.StoreId = ps.StoreId + cert.Certificate = ps.ClientCert + cert.CtCertificateKey = ps.CtClientKey + cert.CertificateKeyHmac = ps.ClientCertKeyHmac + cert.KeyId = ps.ClientKeyId + cs.privateClientCert = cert + } + return cs +} + +func (ps *privateStore) token() *Token { + if ps.TokenHmac != nil { + tk := allocToken() + tk.StoreId = ps.StoreId + tk.TokenHmac = ps.TokenHmac + tk.LastRenewalTime = ps.TokenLastRenewalTime + tk.ExpirationTime = ps.TokenExpirationTime + tk.CreateTime = ps.TokenCreateTime + tk.UpdateTime = ps.TokenUpdateTime + tk.CtToken = ps.CtToken + tk.KeyId = ps.TokenKeyId + tk.Status = ps.TokenStatus + + return tk + } + + return nil +} + +func (ps *privateStore) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(privateStore).decrypt" + + if ps.CtToken != nil { + type ptk struct { + Token []byte `wrapping:"pt,token_data"` + CtToken []byte `wrapping:"ct,token_data"` + } + ptkv := &ptk{ + CtToken: ps.CtToken, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, ptkv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("token")) + } + ps.Token = ptkv.Token + } + + if ps.CtClientKey != nil && ps.ClientCert != nil { + type pck struct { + Key []byte `wrapping:"pt,key_data"` + CtKey []byte `wrapping:"ct,key_data"` + } + pckv := &pck{ + CtKey: ps.CtClientKey, + } + if err := structwrapping.UnwrapStruct(ctx, cipher, pckv, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt), errors.WithMsg("client certificate")) + } + ps.ClientKey = pckv.Key + } + return nil +} + +func (ps *privateStore) client() (*client, error) { + const op = "vault.(privateStore).client" + clientConfig := &clientConfig{ + Addr: ps.VaultAddress, + Token: ps.Token, + CaCert: ps.CaCert, + TlsServerName: ps.TlsServerName, + TlsSkipVerify: ps.TlsSkipVerify, + Namespace: ps.Namespace, + } + + if ps.ClientKey != nil { + clientConfig.ClientCert = ps.ClientCert + clientConfig.ClientKey = ps.ClientKey + } + + client, err := newClient(clientConfig) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client")) + } + return client, nil +} + +// GetPublicId returns the public id. +func (ps *privateStore) GetPublicId() string { return ps.PublicId } + +// TableName returns the table name for gorm. +func (ps *privateStore) TableName() string { + return "credential_vault_store_private" +} diff --git a/internal/credential/vault/private_store_test.go b/internal/credential/vault/private_store_test.go new file mode 100644 index 000000000..6f9908060 --- /dev/null +++ b/internal/credential/vault/private_store_test.go @@ -0,0 +1,90 @@ +package vault + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_lookupPrivateStore(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + tests := []struct { + name string + tls TestVaultTLS + }{ + { + name: "no-tls-valid-token", + }, + { + name: "server-tls-valid-token", + tls: TestServerTLS, + }, + { + name: "client-tls-valid-token", + tls: TestClientTLS, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls)) + + var opts []Option + if tt.tls == TestServerTLS { + opts = append(opts, WithCACert(v.CaCert)) + } + if tt.tls == TestClientTLS { + opts = append(opts, WithCACert(v.CaCert)) + clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(err) + opts = append(opts, WithClientCert(clientCert)) + } + + _, token := v.CreateToken(t) + + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...) + assert.NoError(err) + require.NotNil(credStoreIn) + orig, err := repo.CreateCredentialStore(ctx, credStoreIn) + assert.NoError(err) + require.NotNil(orig) + + origLookup, err := repo.LookupCredentialStore(ctx, orig.GetPublicId()) + assert.NoError(err) + require.NotNil(origLookup) + assert.NotNil(origLookup.Token()) + assert.Equal(orig.GetPublicId(), origLookup.GetPublicId()) + + got, err := repo.lookupPrivateStore(ctx, orig.GetPublicId()) + assert.NoError(err) + require.NotNil(got) + assert.Equal(orig.GetPublicId(), got.GetPublicId()) + + assert.Equal(token, string(got.Token)) + + if tt.tls == TestClientTLS { + require.NotNil(got.ClientKey) + assert.Equal(v.ClientKey, []byte(got.ClientKey)) + } + }) + } +} diff --git a/internal/credential/vault/public_ids.go b/internal/credential/vault/public_ids.go new file mode 100644 index 000000000..8d733cf6a --- /dev/null +++ b/internal/credential/vault/public_ids.go @@ -0,0 +1,42 @@ +package vault + +import ( + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" +) + +// PublicId prefixes for the resources in the vault package. +const ( + CredentialStorePrefix = "csvlt" + CredentialLibraryPrefix = "clvlt" + DynamicCredentialPrefix = "cdvlt" +) + +func newCredentialStoreId() (string, error) { + id, err := db.NewPublicId(CredentialStorePrefix) + if err != nil { + return "", errors.Wrap(err, "vault.newCredentialStoreId") + } + return id, nil +} + +func newCredentialId() (string, error) { + id, err := db.NewPublicId(DynamicCredentialPrefix) + if err != nil { + return "", errors.Wrap(err, "vault.newCredentialId") + } + return id, nil +} + +func newCredentialLibraryId() (string, error) { + id, err := db.NewPublicId(CredentialLibraryPrefix) + if err != nil { + return "", errors.Wrap(err, "vault.newCredentialLibraryId") + } + return id, nil +} + +func init() { + credential.Register(credential.VaultSubtype, CredentialStorePrefix, CredentialLibraryPrefix, DynamicCredentialPrefix) +} diff --git a/internal/credential/vault/query.go b/internal/credential/vault/query.go new file mode 100644 index 000000000..0a783b488 --- /dev/null +++ b/internal/credential/vault/query.go @@ -0,0 +1,189 @@ +package vault + +const ( + insertTokenQuery = ` +insert into credential_vault_token ( + token_hmac, -- $1 + token, -- $2 + store_id, -- $3 + key_id, -- $4 + status, -- $5 + last_renewal_time, -- $6 + expiration_time -- $7 +) values ( + $1, -- token_hmac + $2, -- token + $3, -- store_id + $4, -- key_id + $5, -- status + $6, -- last_renewal_time + wt_add_seconds_to_now($7) -- expiration_time +); +` + + insertCredentialWithExpirationQuery = ` +insert into credential_vault_credential ( + public_id, -- $1 + library_id, -- $2 + session_id, -- $3 + token_hmac, -- $4 + external_id, -- $5 + is_renewable, -- $6 + status, -- $7 + last_renewal_time, -- $8 + expiration_time -- $9 +) values ( + $1, -- public_id + $2, -- library_id + $3, -- session_id + $4, -- token_hmac + $5, -- external_id + $6, -- is_renewable + $7, -- status + $8, -- last_renewal_time + wt_add_seconds_to_now($9) -- expiration_time +); +` + + insertCredentialWithInfiniteExpirationQuery = ` +insert into credential_vault_credential ( + public_id, -- $1 + library_id, -- $2 + session_id, -- $3 + token_hmac, -- $4 + external_id, -- $5 + is_renewable, -- $6 + status, -- $7 + last_renewal_time, -- $8 + expiration_time -- infinity +) values ( + $1, -- public_id + $2, -- library_id + $3, -- session_id + $4, -- token_hmac + $5, -- external_id + $6, -- is_renewable + $7, -- status + $8, -- last_renewal_time + 'infinity' -- expiration_time +); +` + + upsertClientCertQuery = ` +insert into credential_vault_client_certificate + (store_id, certificate, certificate_key, certificate_key_hmac, key_id) +values + ($1, $2, $3, $4, $5) +on conflict (store_id) do update + set certificate = excluded.certificate, + certificate_key = excluded.certificate_key, + certificate_key_hmac = excluded.certificate_key_hmac, + key_id = excluded.key_id +returning *; +` + + deleteClientCertQuery = ` +delete from credential_vault_client_certificate + where store_id = $1; +` + + selectPrivateLibrariesQuery = ` +select * + from credential_vault_library_private + where public_id in (%s); +` + + updateSessionCredentialQuery = ` +update session_credential_dynamic + set credential_id = $1 + where library_id = $2 + and session_id = $3 + and credential_purpose = $4 + and credential_id is null +returning *; +` + + updateTokenExpirationQuery = ` +update credential_vault_token + set last_renewal_time = now(), + expiration_time = wt_add_seconds_to_now(?) + where token_hmac = ?; +` + + updateTokenStatusQuery = ` +update credential_vault_token + set status = ? + where token_hmac = ?; +` + + tokenRenewalNextRunInQuery = ` +select extract(epoch from (last_renewal_time + (expiration_time - last_renewal_time) / 2) - now())::int as renewal_in + from credential_vault_token + where expiration_time = ( + select min(expiration_time) + from credential_vault_token + where status in ('current', 'maintaining') + ); +` + + revokeCredentialsQuery = ` +update credential_vault_credential + set status = 'revoke' + where session_id = $1 + and status = 'active'; +` + + updateCredentialStatusByTokenQuery = ` +update credential_vault_credential + set status = ? + where token_hmac = ?; +` + + credentialRenewalNextRunInQuery = ` +select + extract(epoch from (renewal_time - now()))::int as renewal_in + from credential_vault_credential_private + where expiration_time = ( + select min(expiration_time) + from credential_vault_credential_private + where status = 'active' + ); +` + + updateCredentialExpirationQuery = ` +update credential_vault_credential + set last_renewal_time = now(), + expiration_time = wt_add_seconds_to_now(?) + where public_id = ?; +` + + updateCredentialStatusQuery = ` +update credential_vault_credential + set status = ? + where public_id = ?; +` + + softDeleteStoreQuery = ` +update credential_vault_store + set delete_time = now() + where public_id = $1 + and delete_time is null +returning *; +` + + credStoreCleanupWhereClause = ` +delete_time is not null + and public_id not in + ( + select store_id from credential_vault_token + where status = ? + and expiration_time > now() + ) +` + + credCleanupQuery = ` +delete from credential_vault_credential + where session_id is null + and status not in ('active', 'revoke') +` +) diff --git a/internal/credential/vault/repository.go b/internal/credential/vault/repository.go new file mode 100644 index 000000000..6901299c1 --- /dev/null +++ b/internal/credential/vault/repository.go @@ -0,0 +1,52 @@ +package vault + +import ( + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" +) + +// A Repository stores and retrieves the persistent types in the vault +// package. It is not safe to use a repository concurrently. +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + scheduler *scheduler.Scheduler + // defaultLimit provides a default for limiting the number of results + // returned from the repo + defaultLimit int +} + +// NewRepository creates a new Repository. The returned repository should +// only be used for one transaction and it is not safe for concurrent go +// routines to access it. WithLimit option is used as a repo wide default +// limit applied to all ListX methods. +func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, scheduler *scheduler.Scheduler, opt ...Option) (*Repository, error) { + const op = "vault.NewRepository" + switch { + case r == nil: + return nil, errors.New(errors.InvalidParameter, op, "db.Reader") + case w == nil: + return nil, errors.New(errors.InvalidParameter, op, "db.Writer") + case kms == nil: + return nil, errors.New(errors.InvalidParameter, op, "kms") + case scheduler == nil: + return nil, errors.New(errors.InvalidParameter, op, "scheduler") + } + + opts := getOpts(opt...) + if opts.withLimit == 0 { + // zero signals the boundary defaults should be used. + opts.withLimit = db.DefaultLimit + } + + return &Repository{ + reader: r, + writer: w, + kms: kms, + scheduler: scheduler, + defaultLimit: opts.withLimit, + }, nil +} diff --git a/internal/credential/vault/repository_credential_library.go b/internal/credential/vault/repository_credential_library.go new file mode 100644 index 000000000..23f2b3a4a --- /dev/null +++ b/internal/credential/vault/repository_credential_library.go @@ -0,0 +1,259 @@ +package vault + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/sdk/strutil" +) + +// CreateCredentialLibrary inserts l into the repository and returns a new +// CredentialLibrary containing the credential library's PublicId. l is not +// changed. l must contain a valid StoreId. l must not contain a PublicId. +// The PublicId is generated and assigned by this method. +// +// Both l.Name and l.Description are optional. If l.Name is set, it must be +// unique within l.StoreId. +// +// Both l.CreateTime and l.UpdateTime are ignored. +func (r *Repository) CreateCredentialLibrary(ctx context.Context, scopeId string, l *CredentialLibrary, _ ...Option) (*CredentialLibrary, error) { + const op = "vault.(Repository).CreateCredentialLibrary" + if l == nil { + return nil, errors.New(errors.InvalidParameter, op, "nil CredentialLibrary") + } + if l.CredentialLibrary == nil { + return nil, errors.New(errors.InvalidParameter, op, "nil embedded l") + } + if l.StoreId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no store id") + } + if l.VaultPath == "" { + return nil, errors.New(errors.InvalidParameter, op, "no vault path") + } + if l.PublicId != "" { + return nil, errors.New(errors.InvalidParameter, op, "public id not empty") + } + if scopeId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no scope id") + } + l = l.clone() + + if l.HttpMethod == "" { + l.HttpMethod = string(MethodGet) + } + + id, err := newCredentialLibraryId() + if err != nil { + return nil, errors.Wrap(err, op) + } + l.PublicId = id + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var newCredentialLibrary *CredentialLibrary + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + newCredentialLibrary = l.clone() + err := w.Create(ctx, newCredentialLibrary, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_CREATE))) + if err != nil { + return errors.Wrap(err, op) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s: name %s already exists", l.StoreId, l.Name))) + } + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in credential store: %s", l.StoreId))) + } + return newCredentialLibrary, nil +} + +// UpdateCredentialLibrary updates the repository entry for l.PublicId with +// the values in l for the fields listed in fieldMaskPaths. It returns a +// new CredentialLibrary containing the updated values and a count of the +// number of records updated. l is not changed. +// +// l must contain a valid PublicId. Only Name, Description, VaultPath, +// HttpMethod, and HttpRequestBody can be updated. If l.Name is set to a +// non-empty string, it must be unique within l.StoreId. +// +// An attribute of l will be set to NULL in the database if the attribute +// in l is the zero value and it is included in fieldMaskPaths except for +// HttpMethod. If HttpMethod is in the fieldMaskPath but l.HttpMethod +// is not set it will be set to the value "GET". If storage has a value +// for HttpRequestBody when l.HttpMethod is set to GET the update will fail. +func (r *Repository) UpdateCredentialLibrary(ctx context.Context, scopeId string, l *CredentialLibrary, version uint32, fieldMaskPaths []string, _ ...Option) (*CredentialLibrary, int, error) { + const op = "vault.(Repository).UpdateCredentialLibrary" + if l == nil { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing CredentialLibrary") + } + if l.CredentialLibrary == nil { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded CredentialLibrary") + } + if l.PublicId == "" { + return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id") + } + if version == 0 { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + if scopeId == "" { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id") + } + l = l.clone() + + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold(nameField, f): + case strings.EqualFold(descriptionField, f): + case strings.EqualFold(vaultPathField, f): + case strings.EqualFold(httpMethodField, f): + case strings.EqualFold(httpRequestBodyField, f): + default: + return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f) + } + } + var dbMask, nullFields []string + dbMask, nullFields = dbcommon.BuildUpdatePaths( + map[string]interface{}{ + nameField: l.Name, + descriptionField: l.Description, + vaultPathField: l.VaultPath, + httpMethodField: l.HttpMethod, + httpRequestBodyField: l.HttpRequestBody, + }, + fieldMaskPaths, + nil, + ) + + if strutil.StrListContains(nullFields, httpMethodField) { + dbMask = append(dbMask, httpMethodField) + nullFields = strutil.StrListDelete(nullFields, httpMethodField) + l.HttpMethod = string(MethodGet) + } + + if len(dbMask) == 0 && len(nullFields) == 0 { + return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithCode(errors.Encrypt), + errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsUpdated int + var returnedCredentialLibrary *CredentialLibrary + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + returnedCredentialLibrary = l.clone() + var err error + rowsUpdated, err = w.Update(ctx, returnedCredentialLibrary, dbMask, nullFields, + db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithVersion(&version)) + if err == nil && rowsUpdated > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 resource would have been updated") + } + return err + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, + fmt.Sprintf("name %s already exists: %s", l.Name, l.PublicId)) + } + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(l.PublicId)) + } + + return returnedCredentialLibrary, rowsUpdated, nil +} + +// LookupCredentialLibrary returns the CredentialLibrary for publicId. +// Returns nil, nil if no CredentialLibrary is found for publicId. +func (r *Repository) LookupCredentialLibrary(ctx context.Context, publicId string, _ ...Option) (*CredentialLibrary, error) { + const op = "vault.(Repository).LookupCredentialLibrary" + if publicId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no public id") + } + l := allocCredentialLibrary() + l.PublicId = publicId + if err := r.reader.LookupByPublicId(ctx, l); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) + } + return l, nil +} + +// DeleteCredentialLibrary deletes publicId from the repository and returns +// the number of records deleted. +func (r *Repository) DeleteCredentialLibrary(ctx context.Context, scopeId string, publicId string, _ ...Option) (int, error) { + const op = "vault.(Repository).DeleteCredentialLibrary" + if publicId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no public id") + } + if scopeId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no scope id") + } + + l := allocCredentialLibrary() + l.PublicId = publicId + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsDeleted int + _, err = r.writer.DoTx( + ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) (err error) { + dl := l.clone() + rowsDeleted, err = w.Delete(ctx, dl, db.WithOplog(oplogWrapper, l.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err == nil && rowsDeleted > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 CredentialLibrary would have been deleted") + } + return err + }, + ) + + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", l.PublicId))) + } + + return rowsDeleted, nil +} + +// ListCredentialLibraries returns a slice of CredentialLibraries for the +// storeId. WithLimit is the only option supported. +func (r *Repository) ListCredentialLibraries(ctx context.Context, storeId string, opt ...Option) ([]*CredentialLibrary, error) { + const op = "vault.(Repository).ListCredentialLibraries" + if storeId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no storeId") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var libs []*CredentialLibrary + err := r.reader.SearchWhere(ctx, &libs, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(err, op) + } + return libs, nil +} diff --git a/internal/credential/vault/repository_credential_library_test.go b/internal/credential/vault/repository_credential_library_test.go new file mode 100644 index 000000000..ba9dbe52f --- /dev/null +++ b/internal/credential/vault/repository_credential_library_test.go @@ -0,0 +1,1185 @@ +package vault + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + dbassert "github.com/hashicorp/boundary/internal/db/assert" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestRepository_CreateCredentialLibrary(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + + tests := []struct { + name string + in *CredentialLibrary + opts []Option + want *CredentialLibrary + wantErr errors.Code + }{ + { + name: "nil-CredentialLibrary", + wantErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-CredentialLibrary", + in: &CredentialLibrary{}, + wantErr: errors.InvalidParameter, + }, + { + name: "invalid-no-store-id", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{}, + }, + wantErr: errors.InvalidParameter, + }, + { + name: "invalid-public-id-set", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + PublicId: "abcd_OOOOOOOOOO", + }, + }, + wantErr: errors.InvalidParameter, + }, + { + name: "invalid-no-vault-path", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + }, + }, + wantErr: errors.InvalidParameter, + }, + { + name: "valid-no-options", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + }, + { + name: "valid-with-name", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + Name: "test-name-repo", + VaultPath: "/some/path", + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + Name: "test-name-repo", + VaultPath: "/some/path", + }, + }, + }, + { + name: "valid-with-description", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + Description: "test-description-repo", + VaultPath: "/some/path", + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + Description: "test-description-repo", + VaultPath: "/some/path", + }, + }, + }, + { + name: "valid-POST-method", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "POST", + Description: "test-description-repo", + VaultPath: "/some/path", + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "POST", + Description: "test-description-repo", + VaultPath: "/some/path", + }, + }, + }, + { + name: "valid-POST-http-body", + in: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "POST", + Description: "test-description-repo", + VaultPath: "/some/path", + HttpRequestBody: []byte(`{"common_name":"boundary.com"}`), + }, + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "POST", + Description: "test-description-repo", + VaultPath: "/some/path", + HttpRequestBody: []byte(`{"common_name":"boundary.com"}`), + }, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + got, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), tt.in, tt.opts...) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + assert.Empty(tt.in.PublicId) + require.NotNil(got) + assertPublicId(t, CredentialLibraryPrefix, got.GetPublicId()) + assert.NotSame(tt.in, got) + assert.Equal(tt.want.Name, got.Name) + assert.Equal(tt.want.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) + }) + } + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + in := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: cs.GetPublicId(), + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + } + + got, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in) + require.NoError(err) + require.NotNil(got) + assertPublicId(t, CredentialLibraryPrefix, got.GetPublicId()) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + got2, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err: %q got: %q", errors.NotUnique, err) + assert.Nil(got2) + }) + + t.Run("valid-duplicate-names-diff-stores", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + + csA, csB := css[0], css[1] + + in := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + } + in2 := in.clone() + + in.StoreId = csA.GetPublicId() + got, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in) + require.NoError(err) + require.NotNil(got) + assertPublicId(t, CredentialLibraryPrefix, got.GetPublicId()) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + in2.StoreId = csB.GetPublicId() + got2, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in2) + require.NoError(err) + require.NotNil(got2) + assertPublicId(t, CredentialLibraryPrefix, got2.GetPublicId()) + assert.NotSame(in2, got2) + assert.Equal(in2.Name, got2.Name) + assert.Equal(in2.Description, got2.Description) + assert.Equal(got2.CreateTime, got2.UpdateTime) + }) +} + +func TestRepository_UpdateCredentialLibrary(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + changeHttpRequestBody := func(b []byte) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.HttpRequestBody = b + return l + } + } + + changeHttpMethod := func(m Method) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.HttpMethod = string(m) + return l + } + } + + makeHttpMethodEmptyString := func() func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.HttpMethod = "" + return l + } + } + + changeVaultPath := func(p string) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.VaultPath = p + return l + } + } + + changeName := func(n string) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.Name = n + return l + } + } + + changeDescription := func(d string) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.Description = d + return l + } + } + + makeNil := func() func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + return nil + } + } + + makeEmbeddedNil := func() func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + return &CredentialLibrary{} + } + } + + deletePublicId := func() func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.PublicId = "" + return l + } + } + + nonExistentPublicId := func() func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + l.PublicId = "abcd_OOOOOOOOOO" + return l + } + } + + combine := func(fns ...func(l *CredentialLibrary) *CredentialLibrary) func(*CredentialLibrary) *CredentialLibrary { + return func(l *CredentialLibrary) *CredentialLibrary { + for _, fn := range fns { + l = fn(l) + } + return l + } + } + + tests := []struct { + name string + orig *CredentialLibrary + chgFn func(*CredentialLibrary) *CredentialLibrary + masks []string + want *CredentialLibrary + wantCount int + wantErr errors.Code + }{ + { + name: "nil-credential-library", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: makeNil(), + masks: []string{nameField, descriptionField}, + wantErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-credential-library", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: makeEmbeddedNil(), + masks: []string{nameField, descriptionField}, + wantErr: errors.InvalidParameter, + }, + { + name: "no-public-id", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: deletePublicId(), + masks: []string{nameField, descriptionField}, + wantErr: errors.InvalidPublicId, + }, + { + name: "updating-non-existent-credential-library", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")), + masks: []string{nameField}, + wantErr: errors.RecordNotFound, + }, + { + name: "empty-field-mask", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + wantErr: errors.EmptyFieldMask, + }, + { + name: "read-only-fields-in-field-mask", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"PublicId", "CreateTime", "UpdateTime", "StoreId"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "unknown-field-in-field-mask", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"Bilbo"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "change-name", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{nameField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-update-name-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-description", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Description: "test-description-repo", + }, + }, + chgFn: changeDescription("test-update-description-repo"), + masks: []string{descriptionField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-name-and-description", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")), + masks: []string{nameField, descriptionField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-update-name-repo", + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "delete-name", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{nameField}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Description: "test-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "delete-description", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{descriptionField}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-name", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{descriptionField}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-description", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{nameField}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-update-name-repo", + Description: "test-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-vault-path", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/old/path", + }, + }, + chgFn: changeVaultPath("/new/path"), + masks: []string{vaultPathField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/new/path", + }, + }, + wantCount: 1, + }, + { + name: "delete-vault-path", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: changeVaultPath(""), + masks: []string{vaultPathField}, + wantErr: errors.NotNull, + }, + { + name: "change-http-method", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: changeHttpMethod(MethodPost), + masks: []string{httpMethodField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + }, + }, + wantCount: 1, + }, + { + name: "delete-http-method", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + }, + }, + chgFn: makeHttpMethodEmptyString(), + masks: []string{httpMethodField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + wantCount: 1, + }, + { + name: "add-http-request-body", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + }, + }, + chgFn: changeHttpRequestBody([]byte("new request body")), + masks: []string{httpRequestBodyField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("new request body"), + }, + }, + wantCount: 1, + }, + { + name: "delete-http-request-body", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("request body"), + }, + }, + chgFn: changeHttpRequestBody(nil), + masks: []string{httpRequestBodyField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + }, + }, + wantCount: 1, + }, + { + name: "change-http-request-body", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("old request body"), + }, + }, + chgFn: changeHttpRequestBody([]byte("new request body")), + masks: []string{httpRequestBodyField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("new request body"), + }, + }, + wantCount: 1, + }, + { + name: "change-method-to-GET-leave-request-body", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("old request body"), + }, + }, + chgFn: changeHttpMethod(MethodGet), + masks: []string{httpMethodField}, + wantErr: errors.CheckConstraint, + }, + { + name: "change-method-to-POST-add-request-body", + orig: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + }, + }, + chgFn: combine(changeHttpRequestBody([]byte("new request body")), changeHttpMethod(MethodPost)), + masks: []string{httpRequestBodyField, httpMethodField}, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "POST", + VaultPath: "/some/path", + HttpRequestBody: []byte("new request body"), + }, + }, + wantCount: 1, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + + tt.orig.StoreId = cs.GetPublicId() + orig, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), tt.orig) + assert.NoError(err) + require.NotNil(orig) + + if tt.chgFn != nil { + orig = tt.chgFn(orig) + } + got, gotCount, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), orig, 1, tt.masks) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.Nil(got) + return + } + assert.NoError(err) + assert.Empty(tt.orig.PublicId) + require.NotNil(got) + assertPublicId(t, CredentialLibraryPrefix, got.GetPublicId()) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.NotSame(tt.orig, got) + assert.Equal(tt.orig.StoreId, got.StoreId) + dbassert := dbassert.New(t, conn.DB()) + if tt.want.Name == "" { + dbassert.IsNull(got, "name") + return + } + assert.Equal(tt.want.Name, got.Name) + if tt.want.Description == "" { + dbassert.IsNull(got, "description") + return + } + assert.Equal(tt.want.Description, got.Description) + if tt.wantCount > 0 { + assert.NoError(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + } + }) + } + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + name := "test-dup-name" + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + libs := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) + + lA, lB := libs[0], libs[1] + + lA.Name = name + got1, gotCount1, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), lA, 1, []string{"name"}) + assert.NoError(err) + require.NotNil(got1) + assert.Equal(name, got1.Name) + assert.Equal(1, gotCount1, "row count") + assert.NoError(db.TestVerifyOplog(t, rw, lA.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + + lB.Name = name + got2, gotCount2, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), lB, 1, []string{"name"}) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + assert.Equal(db.NoRowsAffected, gotCount2, "row count") + err = db.TestVerifyOplog(t, rw, lB.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.IsNotFoundError(err)) + }) + + t.Run("valid-duplicate-names-diff-CredentialStores", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + + csA, csB := css[0], css[1] + + in := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + HttpMethod: "GET", + VaultPath: "/some/path", + Name: "test-name-repo", + }, + } + in2 := in.clone() + + in.StoreId = csA.GetPublicId() + got, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in) + assert.NoError(err) + require.NotNil(got) + assertPublicId(t, CredentialLibraryPrefix, got.GetPublicId()) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + + in2.StoreId = csB.GetPublicId() + in2.Name = "first-name" + got2, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), in2) + assert.NoError(err) + require.NotNil(got2) + got2.Name = got.Name + got3, gotCount3, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), got2, 1, []string{"name"}) + assert.NoError(err) + require.NotNil(got3) + assert.NotSame(got2, got3) + assert.Equal(got.Name, got3.Name) + assert.Equal(got2.Description, got3.Description) + assert.Equal(1, gotCount3, "row count") + assert.NoError(db.TestVerifyOplog(t, rw, got2.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + }) + + t.Run("change-scope-id", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + + csA, csB := css[0], css[1] + + lA := TestCredentialLibraries(t, conn, wrapper, csA.GetPublicId(), 1)[0] + lB := TestCredentialLibraries(t, conn, wrapper, csB.GetPublicId(), 1)[0] + + assert.NotEqual(lA.StoreId, lB.StoreId) + orig := lA.clone() + + lA.StoreId = lB.StoreId + assert.Equal(lA.StoreId, lB.StoreId) + + got1, gotCount1, err := repo.UpdateCredentialLibrary(ctx, prj.GetPublicId(), lA, 1, []string{"name"}) + + assert.NoError(err) + require.NotNil(got1) + assert.Equal(orig.StoreId, got1.StoreId) + assert.Equal(1, gotCount1, "row count") + assert.NoError(db.TestVerifyOplog(t, rw, lA.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + }) +} + +func TestRepository_LookupCredentialLibrary(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + l := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0] + + badId, err := newCredentialLibraryId() + require.NoError(t, err) + require.NotNil(t, badId) + + tests := []struct { + name string + in string + want *CredentialLibrary + wantErr errors.Code + }{ + { + name: "valid", + in: l.GetPublicId(), + want: l, + }, + { + name: "empty-public-id", + in: "", + wantErr: errors.InvalidParameter, + }, + { + name: "not-found", + in: badId, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + got, err := repo.LookupCredentialLibrary(ctx, tt.in) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + + switch { + case tt.want == nil: + assert.Nil(got) + case tt.want != nil: + assert.NotNil(got) + assert.Equal(got, tt.want) + } + }) + } +} + +func TestRepository_DeleteCredentialLibrary(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + l := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0] + + badId, err := newCredentialLibraryId() + require.NoError(t, err) + require.NotNil(t, badId) + + tests := []struct { + name string + in string + want int + wantErr errors.Code + }{ + { + name: "found", + in: l.GetPublicId(), + want: 1, + }, + { + name: "not-found", + in: badId, + }, + { + name: "empty-public-id", + in: "", + wantErr: errors.InvalidParameter, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + got, err := repo.DeleteCredentialLibrary(ctx, prj.GetPublicId(), tt.in) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + return + } + assert.NoError(err) + assert.Equal(tt.want, got, "row count") + }) + } +} + +func TestRepository_ListCredentialLibraries(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + csA, csB := css[0], css[1] + + libs := TestCredentialLibraries(t, conn, wrapper, csA.GetPublicId(), 3) + + tests := []struct { + name string + in string + opts []Option + want []*CredentialLibrary + wantErr errors.Code + }{ + { + name: "with-no-credential-store-id", + wantErr: errors.InvalidParameter, + }, + { + name: "CredentialStore-with-no-libraries", + in: csB.GetPublicId(), + want: []*CredentialLibrary{}, + }, + { + name: "CredentialStore-with-libraries", + in: csA.GetPublicId(), + want: libs, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.ListCredentialLibraries(ctx, tt.in, tt.opts...) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + opts := []cmp.Option{ + cmpopts.SortSlices(func(x, y *CredentialLibrary) bool { return x.PublicId < y.PublicId }), + protocmp.Transform(), + } + assert.Empty(cmp.Diff(tt.want, got, opts...)) + }) + } +} + +func TestRepository_ListCredentialLibraries_Limits(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + sche := scheduler.TestScheduler(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + const count = 10 + libs := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), count) + + tests := []struct { + name string + repoOpts []Option + listOpts []Option + wantLen int + }{ + { + name: "with-no-limits", + wantLen: count, + }, + { + name: "with-repo-limit", + repoOpts: []Option{WithLimit(3)}, + wantLen: 3, + }, + { + name: "with-negative-repo-limit", + repoOpts: []Option{WithLimit(-1)}, + wantLen: count, + }, + { + name: "with-list-limit", + listOpts: []Option{WithLimit(3)}, + wantLen: 3, + }, + { + name: "with-negative-list-limit", + listOpts: []Option{WithLimit(-1)}, + wantLen: count, + }, + { + name: "with-repo-smaller-than-list-limit", + repoOpts: []Option{WithLimit(2)}, + listOpts: []Option{WithLimit(6)}, + wantLen: 6, + }, + { + name: "with-repo-larger-than-list-limit", + repoOpts: []Option{WithLimit(6)}, + listOpts: []Option{WithLimit(2)}, + wantLen: 2, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche, tt.repoOpts...) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.ListCredentialLibraries(ctx, libs[0].StoreId, tt.listOpts...) + require.NoError(err) + assert.Len(got, tt.wantLen) + }) + } +} diff --git a/internal/credential/vault/repository_credential_store.go b/internal/credential/vault/repository_credential_store.go new file mode 100644 index 000000000..4540c7f3e --- /dev/null +++ b/internal/credential/vault/repository_credential_store.go @@ -0,0 +1,727 @@ +package vault + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + dbcommon "github.com/hashicorp/boundary/internal/db/common" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/sdk/parseutil" + vault "github.com/hashicorp/vault/api" +) + +// CreateCredentialStore inserts cs into the repository and returns a new +// CredentialStore containing the credential store's PublicId. cs is not +// changed. cs must not contain a PublicId. The PublicId is generated and +// assigned by this method. cs must contain a valid ScopeId, VaultAddress, +// and Vault token. The Vault token must be renewable, periodic, and +// orphan. CreateCredentialStore calls the /auth/token/renew-self and +// /auth/token/lookup-self Vault endpoints. +// +// Both cs.Name and cs.Description are optional. If cs.Name is set, it must +// be unique within cs.ScopeId. Both cs.CreateTime and cs.UpdateTime are +// ignored. +// +// For more information about the required properties of the Vault token see: +// https://www.vaultproject.io/api-docs/auth/token#period, +// https://www.vaultproject.io/api-docs/auth/token#renewable, +// https://www.vaultproject.io/docs/concepts/tokens#token-hierarchies-and-orphan-tokens, +// https://www.vaultproject.io/docs/concepts/tokens#periodic-tokens, and +// https://www.vaultproject.io/docs/concepts/tokens#token-time-to-live-periodic-tokens-and-explicit-max-ttls. +// +// For more information about the Vault endpoints called by +// CreateCredentialStore see: +// https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self and +// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token-self. +func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialStore, _ ...Option) (*CredentialStore, error) { + const op = "vault.(Repository).CreateCredentialStore" + if cs == nil { + return nil, errors.New(errors.InvalidParameter, op, "nil CredentialStore") + } + if cs.CredentialStore == nil { + return nil, errors.New(errors.InvalidParameter, op, "nil embedded CredentialStore") + } + if cs.ScopeId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no scope id") + } + if len(cs.inputToken) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no vault token") + } + if cs.VaultAddress == "" { + return nil, errors.New(errors.InvalidParameter, op, "no vault address") + } + if cs.PublicId != "" { + return nil, errors.New(errors.InvalidParameter, op, "public id not empty") + } + if cs.clientCert != nil && len(cs.clientCert.CertificateKey) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "client certificate without private key") + } + + cs = cs.clone() + + id, err := newCredentialStoreId() + if err != nil { + return nil, errors.Wrap(err, op) + } + cs.PublicId = id + if cs.clientCert != nil { + cs.clientCert.StoreId = id + } + + client, err := cs.client() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create vault client")) + } + tokenLookup, err := client.lookupToken() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to lookup vault token")) + } + if err := validateTokenLookup(op, tokenLookup); err != nil { + return nil, err + } + + available, err := client.capabilities(requiredCapabilities.paths()) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault capabilities")) + } + missing := available.missing(requiredCapabilities) + if len(missing) > 0 { + return nil, + errors.New(errors.VaultTokenMissingCapabilities, op, fmt.Sprintf("missing capabilites: %v", missing)) + } + + renewedToken, err := client.renewToken() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to renew vault token")) + } + + tokenExpires, err := renewedToken.TokenTTL() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration")) + } + + accessor, err := renewedToken.TokenAccessor() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get vault token accessor")) + } + + token, err := newToken(id, cs.inputToken, []byte(accessor), tokenExpires) + if err != nil { + return nil, err + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + + // encrypt + if err := token.encrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(err, op) + } + if cs.clientCert != nil { + if err := cs.clientCert.encrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(err, op) + } + } + + var newToken *Token + var newClientCertificate *ClientCertificate + var newCredentialStore *CredentialStore + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 3) + ticket, err := w.GetTicket(cs) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + + // insert credential store + newCredentialStore = cs.clone() + var csOplogMsg oplog.Message + if err := w.Create(ctx, newCredentialStore, db.NewOplogMsg(&csOplogMsg)); err != nil { + return errors.Wrap(err, op) + } + msgs = append(msgs, &csOplogMsg) + + // insert token + newToken = token.clone() + query, values := newToken.insertQuery() + rows, err := w.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if rows > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 token would have been created") + } + msgs = append(msgs, newToken.oplogMessage(db.CreateOp)) + + newCredentialStore.inputToken = nil + newToken.Token.Token = nil + newToken.Token.CtToken = nil + newCredentialStore.outputToken = newToken + + // insert client certificate (if exists) + if cs.clientCert != nil { + newClientCertificate = cs.clientCert.clone() + var clientCertOplogMsg oplog.Message + if err := w.Create(ctx, newClientCertificate, db.NewOplogMsg(&clientCertOplogMsg)); err != nil { + return errors.Wrap(err, op) + } + msgs = append(msgs, &clientCertOplogMsg) + + newClientCertificate.CertificateKey = nil + newClientCertificate.CtCertificateKey = nil + newCredentialStore.clientCert = newClientCertificate + + } + metadata := cs.oplog(oplog.OpType_OP_TYPE_CREATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", cs.ScopeId, cs.Name))) + } + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", cs.ScopeId))) + } + + // Best effort update next run time of token renewal job, but an error should not + // cause update to fail. + // TODO (lcr 05/2021): log error once repo has logger + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRenewalJobName, token.renewalIn()) + + return newCredentialStore, nil +} + +func validateTokenLookup(op errors.Op, s *vault.Secret) error { + if s.Data == nil { + return errors.New(errors.InvalidParameter, op, "vault secret is not a token lookup") + } + + if s.Data["renewable"] == nil { + return errors.E(errors.WithCode(errors.VaultTokenNotRenewable), errors.WithOp(op)) + } + renewable, err := parseutil.ParseBool(s.Data["renewable"]) + if err != nil { + return errors.Wrap(err, op) + } + if !renewable { + return errors.E(errors.WithCode(errors.VaultTokenNotRenewable), errors.WithOp(op)) + } + + if s.Data["orphan"] == nil { + return errors.E(errors.WithCode(errors.VaultTokenNotOrphan), errors.WithOp(op)) + } + orphan, err := parseutil.ParseBool(s.Data["orphan"]) + if err != nil { + return errors.Wrap(err, op) + } + if !orphan { + return errors.E(errors.WithCode(errors.VaultTokenNotOrphan), errors.WithOp(op)) + } + + if s.Data["period"] == nil { + return errors.E(errors.WithCode(errors.VaultTokenNotPeriodic), errors.WithOp(op)) + } + + return nil +} + +// LookupCredentialStore returns the CredentialStore for publicId. Returns +// nil, nil if no CredentialStore is found for publicId. +func (r *Repository) LookupCredentialStore(ctx context.Context, publicId string, _ ...Option) (*CredentialStore, error) { + const op = "vault.(Repository).LookupCredentialStore" + if publicId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no public id") + } + agg := allocPublicStore() + agg.PublicId = publicId + if err := r.reader.LookupByPublicId(ctx, agg); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) + } + return agg.toCredentialStore(), nil +} + +type publicStore struct { + PublicId string `gorm:"primary_key"` + ScopeId string + Name string + Description string + CreateTime *timestamp.Timestamp + UpdateTime *timestamp.Timestamp + Version uint32 + VaultAddress string + Namespace string + CaCert []byte + TlsServerName string + TlsSkipVerify bool + TokenHmac []byte + TokenCreateTime *timestamp.Timestamp + TokenUpdateTime *timestamp.Timestamp + TokenLastRenewalTime *timestamp.Timestamp + TokenExpirationTime *timestamp.Timestamp + ClientCert []byte + ClientCertKeyHmac []byte +} + +func allocPublicStore() *publicStore { + return &publicStore{} +} + +func (ps *publicStore) toCredentialStore() *CredentialStore { + cs := allocCredentialStore() + cs.PublicId = ps.PublicId + cs.ScopeId = ps.ScopeId + cs.Name = ps.Name + cs.Description = ps.Description + cs.CreateTime = ps.CreateTime + cs.UpdateTime = ps.UpdateTime + cs.Version = ps.Version + cs.VaultAddress = ps.VaultAddress + cs.Namespace = ps.Namespace + cs.CaCert = ps.CaCert + cs.TlsServerName = ps.TlsServerName + cs.TlsSkipVerify = ps.TlsSkipVerify + + if ps.TokenHmac != nil { + tk := allocToken() + tk.TokenHmac = ps.TokenHmac + tk.LastRenewalTime = ps.TokenLastRenewalTime + tk.ExpirationTime = ps.TokenExpirationTime + tk.CreateTime = ps.TokenCreateTime + tk.UpdateTime = ps.TokenUpdateTime + cs.outputToken = tk + } + + if ps.ClientCert != nil { + cert := allocClientCertificate() + cert.Certificate = ps.ClientCert + cert.CertificateKeyHmac = ps.ClientCertKeyHmac + cs.clientCert = cert + } + return cs +} + +// TableName returns the table name for gorm. +func (_ *publicStore) TableName() string { return "credential_vault_store_public" } + +// GetPublicId returns the public id. +func (ps *publicStore) GetPublicId() string { return ps.PublicId } + +// UpdateCredentialStore updates the repository entry for cs.PublicId with +// the values in cs for the fields listed in fieldMaskPaths. It returns a +// new CredentialStore containing the updated values and a count of the +// number of records updated. cs is not changed. +// +// cs must contain a valid PublicId. Only Name, Description, Namespace, +// TlsServerName, TlsSkipVerify, CaCert, VaultAddress, ClientCertificate, +// ClientCertificateKey, and Token can be changed. If cs.Name is set to a +// non-empty string, it must be unique within cs.ScopeId. If Token is changed, +// the new token must have the same properties defined in CreateCredentialStore +// and UpdateCredentialStore calls the same Vault endpoints described in +// CreateCredentialStore. +// +// An attribute of cs will be set to NULL in the database if the attribute +// in cs is the zero value and it is included in fieldMaskPaths. +func (r *Repository) UpdateCredentialStore(ctx context.Context, cs *CredentialStore, version uint32, fieldMaskPaths []string, _ ...Option) (*CredentialStore, int, error) { + const op = "vault.(Repository).UpdateCredentialStore" + if cs == nil { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing CredentialStore") + } + if cs.CredentialStore == nil { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing embedded CredentialStore") + } + if cs.PublicId == "" { + return nil, db.NoRowsAffected, errors.New(errors.InvalidPublicId, op, "missing public id") + } + if version == 0 { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + if cs.ScopeId == "" { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing scope id") + } + cs = cs.clone() + + var validateToken, updateToken bool + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold(nameField, f): + case strings.EqualFold(descriptionField, f): + case strings.EqualFold(namespaceField, f): + case strings.EqualFold(tlsServerNameField, f): + case strings.EqualFold(tlsSkipVerifyField, f): + case strings.EqualFold(caCertField, f): + case strings.EqualFold(vaultAddressField, f): + validateToken = true + case strings.EqualFold(certificateField, f): + case strings.EqualFold(certificateKeyField, f): + case strings.EqualFold(tokenField, f): + if len(cs.inputToken) != 0 { + updateToken = true + validateToken = true + } + default: + return nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, f) + } + } + dbMask, nullFields := dbcommon.BuildUpdatePaths( + map[string]interface{}{ + nameField: cs.Name, + descriptionField: cs.Description, + namespaceField: cs.Namespace, + tlsServerNameField: cs.TlsServerName, + tlsSkipVerifyField: cs.TlsSkipVerify, + caCertField: cs.CaCert, + vaultAddressField: cs.VaultAddress, + tokenField: cs.inputToken, + }, + fieldMaskPaths, + []string{ + tlsSkipVerifyField, + }, + ) + var clientCert, clientCertKey []byte + if cs.ClientCertificate() != nil { + clientCert = cs.ClientCertificate().GetCertificate() + clientCertKey = cs.ClientCertificate().GetCertificateKey() + } + certDbMask, certNullFields := dbcommon.BuildUpdatePaths( + map[string]interface{}{ + certificateField: clientCert, + certificateKeyField: clientCertKey, + }, + fieldMaskPaths, nil, + ) + if len(certNullFields) != 0 && len(certNullFields) != 2 { + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "attempting to unset a required field on a client cert") + } + if len(append(dbMask, certDbMask...)) == 0 && len(append(nullFields, certNullFields...)) == 0 { + return nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "missing field mask") + } + + var filteredDbMask, filteredNullFields []string + for _, f := range dbMask { + switch { + case strings.EqualFold(tokenField, f): + default: + filteredDbMask = append(filteredDbMask, f) + } + } + for _, f := range nullFields { + switch { + case strings.EqualFold(tokenField, f): + return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "attempting to unset the value for token") + default: + filteredNullFields = append(filteredNullFields, f) + } + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, + errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + databaseWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, db.NoRowsAffected, + errors.Wrap(err, op, errors.WithMsg("unable to get database wrapper")) + } + + ps, err := r.lookupPrivateStore(ctx, cs.GetPublicId()) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to lookup private credential store")) + } + if ps == nil { + return nil, db.NoRowsAffected, errors.New(errors.RecordNotFound, op, fmt.Sprintf("credential store %s", cs.PublicId)) + } + origStore := ps.toCredentialStore() + origStore.inputToken = ps.Token + if len(ps.ClientCert) > 0 { + origStore.clientCert, err = NewClientCertificate(ps.ClientCert, ps.ClientKey) + } + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("can't recreate client certificate for vault client creation")) + } + updatedStore := origStore.applyUpdate(cs, fieldMaskPaths) + + if len(certDbMask) > 0 && updatedStore.clientCert != nil { + if err := updatedStore.clientCert.encrypt(ctx, databaseWrapper); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op) + } + } + + var token *Token + client, err := updatedStore.client() + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get client for updated store")) + } + if validateToken { + tokenLookup, err := client.lookupToken() + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("cannot lookup token for updated store")) + } + if err := validateTokenLookup(op, tokenLookup); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op) + } + + available, err := client.capabilities(requiredCapabilities.paths()) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault capabilities")) + } + missing := available.missing(requiredCapabilities) + if len(missing) > 0 { + return nil, + db.NoRowsAffected, + errors.New(errors.VaultTokenMissingCapabilities, op, fmt.Sprintf("missing capabilites: %v", missing)) + } + } + if updateToken { + renewedToken, err := client.renewToken() + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to renew vault token")) + } + tokenExpires, err := renewedToken.TokenTTL() + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault token expiration")) + } + accessor, err := renewedToken.TokenAccessor() + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get vault token accessor")) + } + if token, err = newToken(cs.GetPublicId(), cs.inputToken, []byte(accessor), tokenExpires); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op) + } + // encrypt token + if err := token.encrypt(ctx, databaseWrapper); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(err, op) + } + } + + var rowsUpdated int + var returnedCredentialStore *CredentialStore + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 3) + ticket, err := w.GetTicket(cs) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + + cs := cs.clone() + var csOplogMsg oplog.Message + + switch { + case len(filteredDbMask) == 0 && len(filteredNullFields) == 0: + // the credential store's fields are not being updated, + // just it's token or client certificate, so we need to + // just update the credential store's version. + cs.Version = version + 1 + rowsUpdated, err = w.Update(ctx, cs, []string{"Version"}, nil, db.NewOplogMsg(&csOplogMsg), db.WithVersion(&version)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update credential store version")) + } + switch rowsUpdated { + case 1: + case 0: + return nil + default: + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated credential store version and %d rows updated", rowsUpdated)) + } + default: + rowsUpdated, err = w.Update(ctx, cs, filteredDbMask, filteredNullFields, db.NewOplogMsg(&csOplogMsg), db.WithVersion(&version)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update credential store")) + } + switch rowsUpdated { + case 1: + case 0: + return nil + default: + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated credential store and %d rows updated", rowsUpdated)) + } + } + msgs = append(msgs, &csOplogMsg) + + switch { + case len(certNullFields) == 2: + // Delete the certificate + deleteCert := allocClientCertificate() + deleteCert.StoreId = cs.GetPublicId() + query, values := deleteCert.deleteQuery() + rows, err := w.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to delete client certificate")) + } + if rows > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 client certificate would have been deleted") + } + msgs = append(msgs, deleteCert.oplogMessage(db.DeleteOp)) + case len(certDbMask) > 0: + if updatedStore.clientCert == nil { + return errors.New(errors.InvalidParameter, op, "updated cert") + } + query, values := updatedStore.clientCert.insertQuery() + rows, err := w.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to upsert client certificate")) + } + if rows > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 client certificate would have been upserted") + } + } + + if updateToken { + query, values := token.insertQuery() + rows, err := w.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if rows > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 token would have been created") + } + msgs = append(msgs, token.oplogMessage(db.CreateOp)) + } + + publicId := cs.PublicId + agg := allocPublicStore() + agg.PublicId = publicId + if err := reader.LookupByPublicId(ctx, agg); err != nil { + return errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("unable to lookup credential store: %s", publicId))) + } + returnedCredentialStore = agg.toCredentialStore() + + metadata := cs.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + return nil + }, + ) + + if err != nil { + if errors.IsUniqueError(err) { + return nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, + fmt.Sprintf("name %s already exists: %s", cs.Name, cs.PublicId)) + } + return nil, db.NoRowsAffected, err + } + + if updateToken && token != nil { + // Best effort update next run time of token renewal job, but an error should not + // cause update to fail. + // TODO (lcr 05/2021): log error once repo has logger + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRenewalJobName, token.renewalIn()) + } + + return returnedCredentialStore, rowsUpdated, nil +} + +// ListCredentialStores returns a slice of CredentialStores for the +// scopeIds. WithLimit is the only option supported. +func (r *Repository) ListCredentialStores(ctx context.Context, scopeIds []string, opt ...Option) ([]*CredentialStore, error) { + const op = "vault.(Repository).ListCredentialStores" + if len(scopeIds) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no scopeIds") + } + opts := getOpts(opt...) + limit := r.defaultLimit + if opts.withLimit != 0 { + // non-zero signals an override of the default limit for the repo. + limit = opts.withLimit + } + var credentialStores []*publicStore + err := r.reader.SearchWhere(ctx, &credentialStores, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(err, op) + } + var out []*CredentialStore + for _, ca := range credentialStores { + out = append(out, ca.toCredentialStore()) + } + return out, nil +} + +// DeleteCredentialStore deletes publicId from the repository and returns +// the number of records deleted. All options are ignored. +func (r *Repository) DeleteCredentialStore(ctx context.Context, publicId string, _ ...Option) (int, error) { + const op = "vault.(Repository).DeleteCredentialStore" + if publicId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no public id") + } + + cs := allocCredentialStore() + cs.PublicId = publicId + if err := r.reader.LookupByPublicId(ctx, cs); err != nil { + if errors.IsNotFoundError(err) { + return db.NoRowsAffected, nil + } + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) + } + if cs.ScopeId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "no scope id") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rows int + query, values := cs.softDeleteQuery() + _, err = r.writer.DoTx( + ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) (err error) { + var msgs []*oplog.Message + ticket, err := w.GetTicket(cs) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + + rows, err = w.Exec(ctx, query, values) + if err != nil { + return errors.Wrap(err, op) + } + if rows > 1 { + return errors.New(errors.MultipleRecords, op, "more than 1 credential store would have been deleted") + } + msg := cs.oplogMessage(db.UpdateOp) + msg.FieldMaskPaths = []string{"DeleteTime"} + msgs = append(msgs, msg) + + metadata := cs.oplog(oplog.OpType_OP_TYPE_UPDATE) + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, ticket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + + return nil + }, + ) + + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("delete failed for %s", cs.PublicId))) + } + + if rows > 0 { + // Schedule token revocation and credential store cleanup jobs to run immediately + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, tokenRevocationJobName, 0) + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, credentialStoreCleanupJobName, 0) + } + return rows, nil +} diff --git a/internal/credential/vault/repository_credential_store_test.go b/internal/credential/vault/repository_credential_store_test.go new file mode 100644 index 000000000..3ce3530fb --- /dev/null +++ b/internal/credential/vault/repository_credential_store_test.go @@ -0,0 +1,1641 @@ +package vault + +import ( + "context" + "fmt" + "net" + "net/url" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + dbassert "github.com/hashicorp/boundary/internal/db/assert" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/scheduler" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/jinzhu/gorm" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" +) + +func TestRepository_CreateCredentialStoreResource(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t) + _, token := v.CreateToken(t) + + in, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), WithName("gary"), WithDescription("46")) + assert.NoError(err) + require.NotNil(in) + assert.NotEmpty(in.Name) + got, err := repo.CreateCredentialStore(ctx, in) + + require.NoError(err) + require.NotNil(got) + assertPublicId(t, CredentialStorePrefix, got.PublicId) + + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + assert.Equal(got.CreateTime, got.UpdateTime) + + got2, err := repo.CreateCredentialStore(ctx, in) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + }) + + t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t) + + _, token1 := v.CreateToken(t) + in1, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token1), WithName("gary"), WithDescription("46")) + assert.NoError(err) + require.NotNil(in1) + assert.NotEmpty(in1.Name) + got1, err := repo.CreateCredentialStore(ctx, in1) + require.NoError(err) + require.NotNil(got1) + assertPublicId(t, CredentialStorePrefix, got1.PublicId) + assert.NotSame(in1, got1) + assert.Equal(in1.Name, got1.Name) + assert.Equal(in1.Description, got1.Description) + assert.Equal(got1.CreateTime, got1.UpdateTime) + + _, token2 := v.CreateToken(t) + in2, err := NewCredentialStore(org.GetPublicId(), v.Addr, []byte(token2), WithName("gary"), WithDescription("46")) + assert.NoError(err) + require.NotNil(in2) + assert.NotEmpty(in2.Name) + in2.ScopeId = org.GetPublicId() + got2, err := repo.CreateCredentialStore(ctx, in2) + require.NoError(err) + require.NotNil(got2) + assertPublicId(t, CredentialStorePrefix, got2.PublicId) + assert.NotSame(in2, got2) + assert.Equal(in2.Name, got2.Name) + assert.Equal(in2.Description, got2.Description) + assert.Equal(got2.CreateTime, got2.UpdateTime) + + assert.Equal(in1.Name, in2.Name) + assert.Equal(got1.Name, got2.Name) + }) +} + +func assertPublicId(t *testing.T, prefix, actual string) { + t.Helper() + assert.NotEmpty(t, actual) + parts := strings.Split(actual, "_") + assert.Equalf(t, 2, len(parts), "want one '_' in PublicId, got multiple in %q", actual) + assert.Equalf(t, prefix, parts[0], "PublicId want prefix: %q, got: %q in %q", prefix, parts[0], actual) +} + +func TestRepository_CreateCredentialStoreNonResource(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + tests := []struct { + name string + tls TestVaultTLS + tokenOpts []TestOption + wantErr errors.Code + }{ + { + name: "no-tls-valid-token", + }, + { + name: "server-tls-valid-token", + tls: TestServerTLS, + }, + { + name: "client-tls-valid-token", + tls: TestClientTLS, + }, + { + name: "token-missing-capabilities", + tokenOpts: []TestOption{WithPolicies([]string{"default"})}, + wantErr: errors.VaultTokenMissingCapabilities, + }, + { + name: "no-tls-token-not-renewable", + tokenOpts: []TestOption{TestRenewableToken(false)}, + wantErr: errors.VaultTokenNotRenewable, + }, + { + name: "no-tls-token-not-orphan", + tokenOpts: []TestOption{TestOrphanToken(false)}, + wantErr: errors.VaultTokenNotOrphan, + }, + { + name: "no-tls-token-not-periodic", + tokenOpts: []TestOption{TestPeriodicToken(false)}, + wantErr: errors.VaultTokenNotPeriodic, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls)) + _, token := v.CreateToken(t, tt.tokenOpts...) + + var opts []Option + if tt.tls == TestServerTLS { + opts = append(opts, WithCACert(v.CaCert)) + } + if tt.tls == TestClientTLS { + opts = append(opts, WithCACert(v.CaCert)) + clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(err) + opts = append(opts, WithClientCert(clientCert)) + } + + credStoreIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...) + assert.NoError(err) + require.NotNil(credStoreIn) + got, err := repo.CreateCredentialStore(ctx, credStoreIn) + + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + if got != nil { + err := db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) + require.Errorf(err, "should not have found oplog entry for %s", got.PublicId) + } + return + } + require.NoError(err) + assert.Empty(credStoreIn.PublicId) + require.NotNil(got) + assertPublicId(t, CredentialStorePrefix, got.PublicId) + assert.NotSame(credStoreIn, got) + assert.Equal(got.CreateTime, got.UpdateTime) + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) + + outToken := allocToken() + assert.NoError(rw.LookupWhere(ctx, &outToken, "store_id = ?", got.PublicId)) + + if tt.tls == TestClientTLS { + outClientCert := allocClientCertificate() + assert.NoError(rw.LookupWhere(ctx, &outClientCert, "store_id = ?", got.PublicId)) + } + }) + } +} + +func TestRepository_LookupCredentialStore(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + stores := TestCredentialStores(t, conn, wrapper, prj.PublicId, 2) + csWithClientCert := stores[0] + csWithoutClientCert := stores[1] + + ccert := allocClientCertificate() + rows, err := rw.Delete(context.Background(), ccert, db.WithWhere("store_id = ?", csWithoutClientCert.GetPublicId())) + require.NoError(t, err) + require.Equal(t, 1, rows) + + badId, err := newCredentialStoreId() + assert.NoError(t, err) + require.NotNil(t, badId) + + tests := []struct { + name string + id string + want *CredentialStore + wantClientCert bool + wantErr errors.Code + }{ + { + name: "valid-with-client-cert", + id: csWithClientCert.GetPublicId(), + want: csWithClientCert, + wantClientCert: true, + }, + { + name: "valid-without-client-cert", + id: csWithoutClientCert.GetPublicId(), + want: csWithoutClientCert, + wantClientCert: false, + }, + { + name: "empty-public-id", + id: "", + wantErr: errors.InvalidParameter, + }, + { + name: "not-found", + id: badId, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + got, err := repo.LookupCredentialStore(ctx, tt.id) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + + if tt.want == nil { + assert.Nil(got) + return + } + + assert.NotNil(got) + assert.NotSame(got, tt.want) + assert.NotNil(got.Token(), "token") + + if tt.wantClientCert { + assert.NotNil(got.ClientCertificate(), "client certificate") + } else { + assert.Nil(got.ClientCertificate(), "client certificate") + } + }) + } +} + +func TestRepository_UpdateCredentialStore_Attributes(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + changeNamespace := func(n string) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.Namespace = n + return cs + } + } + + changeTlsServerName := func(n string) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.TlsServerName = n + return cs + } + } + + changeTlsSkipVerify := func(t bool) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.TlsSkipVerify = t + return cs + } + } + + changeName := func(n string) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.Name = n + return cs + } + } + + changeDescription := func(d string) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.Description = d + return cs + } + } + + makeNil := func() func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + return nil + } + } + + makeEmbeddedNil := func() func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + return &CredentialStore{} + } + } + + deletePublicId := func() func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.PublicId = "" + return cs + } + } + + nonExistentPublicId := func() func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + cs.PublicId = "abcd_OOOOOOOOOO" + return cs + } + } + + combine := func(fns ...func(cs *CredentialStore) *CredentialStore) func(*CredentialStore) *CredentialStore { + return func(cs *CredentialStore) *CredentialStore { + for _, fn := range fns { + cs = fn(cs) + } + return cs + } + } + + tests := []struct { + name string + orig *CredentialStore + chgFn func(*CredentialStore) *CredentialStore + masks []string + want *CredentialStore + wantCount int + wantErr errors.Code + }{ + { + name: "nil-credential-store", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + chgFn: makeNil(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-credential-store", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + chgFn: makeEmbeddedNil(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "no-public-id", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + chgFn: deletePublicId(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidPublicId, + }, + { + name: "updating-non-existent-credential-store", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")), + masks: []string{"Name"}, + wantErr: errors.RecordNotFound, + }, + { + name: "empty-field-mask", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + wantErr: errors.EmptyFieldMask, + }, + { + name: "read-only-fields-in-field-mask", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"PublicId", "CreateTime", "UpdateTime", "ScopeId"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "unknown-field-in-field-mask", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"Bilbo"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "change-name", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"Name"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-update-name-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-description", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Description: "test-description-repo", + }, + }, + chgFn: changeDescription("test-update-description-repo"), + masks: []string{"Description"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-name-and-description", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")), + masks: []string{"Name", "Description"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-update-name-repo", + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "delete-name", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{"Name"}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Description: "test-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "delete-description", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{"Description"}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-name", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{"Description"}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-update-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-description", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + Description: "test-description-repo", + }, + }, + masks: []string{"Name"}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-update-name-repo", + Description: "test-description-repo", + }, + }, + wantCount: 1, + }, + { + name: "change-namespace", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Namespace: "test-namespace", + }, + }, + chgFn: changeNamespace("test-update-namespace"), + masks: []string{"Namespace"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Namespace: "test-update-namespace", + }, + }, + wantCount: 1, + }, + { + name: "delete-namespace", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Namespace: "test-namespace", + }, + }, + chgFn: changeNamespace(""), + masks: []string{"Namespace"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + wantCount: 1, + }, + { + name: "change-tls-server-name", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsServerName: "tls-server-name", + }, + }, + chgFn: changeTlsServerName("tls-server-name-update"), + masks: []string{"TlsServerName"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsServerName: "tls-server-name-update", + }, + }, + wantCount: 1, + }, + { + name: "delete-tls-server-name", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsServerName: "tls-server-name", + }, + }, + chgFn: changeTlsServerName(""), + masks: []string{"TlsServerName"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + wantCount: 1, + }, + { + name: "tls-skip-verify-false2true", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: false, + }, + }, + chgFn: changeTlsSkipVerify(true), + masks: []string{"TlsSkipVerify"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: true, + }, + }, + wantCount: 1, + }, + { + name: "tls-skip-verify-true2false", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: true, + }, + }, + chgFn: changeTlsSkipVerify(false), + masks: []string{"TlsSkipVerify"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: false, + }, + }, + wantCount: 1, + }, + { + name: "tls-skip-verify-false2false", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: false, + }, + }, + chgFn: changeTlsSkipVerify(false), + masks: []string{"TlsSkipVerify"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: false, + }, + }, + wantCount: 1, + }, + { + name: "tls-skip-verify-true2true", + orig: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: true, + }, + }, + chgFn: changeTlsSkipVerify(true), + masks: []string{"TlsSkipVerify"}, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + TlsSkipVerify: true, + }, + }, + wantCount: 1, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + tt.orig.ScopeId = prj.GetPublicId() + + vs := NewTestVaultServer(t) + + tt.orig.VaultAddress = vs.Addr + if tt.want != nil && tt.want.VaultAddress == "" { + tt.want.VaultAddress = vs.Addr + } + + _, token := vs.CreateToken(t) + tt.orig.inputToken = []byte(token) + + orig, err := repo.CreateCredentialStore(ctx, tt.orig) + assert.NoError(err) + require.NotNil(orig) + + if tt.chgFn != nil { + orig = tt.chgFn(orig) + } + got, gotCount, err := repo.UpdateCredentialStore(ctx, orig, 1, tt.masks) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.Nil(got) + return + } + assert.NoError(err) + assert.Empty(tt.orig.PublicId) + require.NotNil(got) + assertPublicId(t, CredentialStorePrefix, got.PublicId) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.NotSame(tt.orig, got) + assert.Equal(tt.orig.ScopeId, got.ScopeId) + dbassert := dbassert.New(t, conn.DB()) + if tt.want.Name == "" { + dbassert.IsNull(got, "name") + } else { + assert.Equal(tt.want.Name, got.Name) + } + + if tt.want.Description == "" { + dbassert.IsNull(got, "description") + } else { + assert.Equal(tt.want.Description, got.Description) + } + + if tt.want.Namespace == "" { + dbassert.IsNull(got, "namespace") + } else { + assert.Equal(tt.want.Namespace, got.Namespace) + } + + if tt.want.TlsServerName == "" { + dbassert.IsNull(got, "TlsServerName") + } else { + assert.Equal(tt.want.TlsServerName, got.TlsServerName) + } + + assert.Equal(tt.want.TlsSkipVerify, got.TlsSkipVerify) + + assert.Equal(tt.want.VaultAddress, got.VaultAddress) + + if tt.wantCount > 0 { + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + } + }) + } + + t.Run("change-vault-address", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + vs := NewTestVaultServer(t) + _, token := vs.CreateToken(t) + + u, err := url.Parse(vs.Addr) + require.NoError(err) + _, port, err := net.SplitHostPort(u.Host) + require.NoError(err) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &CredentialStore{ + inputToken: []byte(token), + CredentialStore: &store.CredentialStore{ + ScopeId: prj.GetPublicId(), + VaultAddress: fmt.Sprintf("%v://127.0.0.1:%s", u.Scheme, port), + }, + } + + orig, err := repo.CreateCredentialStore(ctx, in) + assert.NoError(err) + require.NotNil(orig) + + // Change Address 127.0.0.1:port -> localhost:port should work + orig.VaultAddress = fmt.Sprintf("%v://localhost:%s", u.Scheme, port) + got, gotCount, err := repo.UpdateCredentialStore(ctx, orig, 1, []string{vaultAddressField}) + assert.NoError(err) + assert.Equal(1, gotCount, "count of updated records") + require.NotNil(got) + assert.Equal(orig.VaultAddress, got.VaultAddress) + + // Update to new address should fail because current token not valid on new address + origAddr := orig.VaultAddress + orig.VaultAddress = "https://vault2.nowhere.com" + got, gotCount, err = repo.UpdateCredentialStore(ctx, orig, 2, []string{vaultAddressField}) + assert.Error(err) + require.Nil(got) + require.Equal(0, gotCount) + + cs, err := repo.LookupCredentialStore(ctx, orig.PublicId) + require.NoError(err) + assert.Equal(origAddr, cs.VaultAddress) + }) + + t.Run("change-ca-cert", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + vs := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS)) + + _, token := vs.CreateToken(t) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &CredentialStore{ + inputToken: []byte(token), + CredentialStore: &store.CredentialStore{ + ScopeId: prj.GetPublicId(), + VaultAddress: vs.Addr, + CaCert: vs.CaCert, + }, + } + + orig, err := repo.CreateCredentialStore(ctx, in) + assert.NoError(err) + require.NotNil(orig) + assert.Equal(vs.CaCert, orig.CaCert) + + // Change CA Cert + orig.CaCert = vs.ServerCert + got1, gotCount1, err := repo.UpdateCredentialStore(ctx, orig, 1, []string{"CaCert"}) + assert.NoError(err) + assert.Equal(1, gotCount1, "count of updated records") + require.NotNil(got1) + assert.Equal(vs.ServerCert, got1.CaCert) + + // Delete CA Cert + orig.CaCert = nil + got2, gotCount2, err := repo.UpdateCredentialStore(ctx, orig, 2, []string{"CaCert"}) + assert.NoError(err) + assert.Equal(1, gotCount2, "count of updated records") + require.NotNil(got2) + assert.Nil(got2.CaCert) + + dbassert := dbassert.New(t, conn.DB()) + dbassert.IsNull(got2, "CaCert") + }) + + t.Run("invalid-duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + name := "test-dup-name" + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + css := TestCredentialStores(t, conn, wrapper, prj.PublicId, 2) + + csA, csB := css[0], css[1] + + csA.Name = name + got1, gotCount1, err := repo.UpdateCredentialStore(ctx, csA, 1, []string{"Name"}) + assert.NoError(err) + require.NotNil(got1) + assert.Equal(name, got1.Name) + assert.Equal(1, gotCount1, "row count") + assert.NoError(db.TestVerifyOplog(t, rw, csA.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + + csB.Name = name + got2, gotCount2, err := repo.UpdateCredentialStore(ctx, csB, 1, []string{"Name"}) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + assert.Equal(db.NoRowsAffected, gotCount2, "row count") + }) + + t.Run("valid-duplicate-names-diff-scopes", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + vs := NewTestVaultServer(t) + + _, token1 := vs.CreateToken(t) + _, token2 := vs.CreateToken(t) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &CredentialStore{ + inputToken: []byte(token1), + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + VaultAddress: vs.Addr, + }, + } + in2 := in.clone() + in2.inputToken = []byte(token2) + + in.ScopeId = prj.GetPublicId() + got, err := repo.CreateCredentialStore(ctx, in) + assert.NoError(err) + require.NotNil(got) + assertPublicId(t, CredentialStorePrefix, got.PublicId) + assert.NotSame(in, got) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + + in2.ScopeId = org.GetPublicId() + in2.Name = "first-name" + got2, err := repo.CreateCredentialStore(ctx, in2) + assert.NoError(err) + require.NotNil(got2) + got2.Name = got.Name + got3, gotCount3, err := repo.UpdateCredentialStore(ctx, got2, 1, []string{"Name"}) + assert.NoError(err) + require.NotNil(got3) + assert.NotSame(got2, got3) + assert.Equal(got.Name, got3.Name) + assert.Equal(got2.Description, got3.Description) + assert.Equal(1, gotCount3, "row count") + }) + + t.Run("change-scope-id", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + iamRepo := iam.TestRepo(t, conn, wrapper) + _, prj1 := iam.TestScopes(t, iamRepo) + _, prj2 := iam.TestScopes(t, iamRepo) + csA, csB := TestCredentialStores(t, conn, wrapper, prj1.PublicId, 1)[0], TestCredentialStores(t, conn, wrapper, prj2.PublicId, 1)[0] + assert.NotEqual(csA.ScopeId, csB.ScopeId) + orig := csA.clone() + + csA.ScopeId = csB.ScopeId + assert.Equal(csA.ScopeId, csB.ScopeId) + + got1, gotCount1, err := repo.UpdateCredentialStore(ctx, csA, 1, []string{"Name"}) + + assert.NoError(err) + require.NotNil(got1) + assert.Equal(orig.ScopeId, got1.ScopeId) + assert.Equal(1, gotCount1, "row count") + }) +} + +func TestRepository_UpdateCredentialStore_VaultToken(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + tests := []struct { + name string + newTokenOpts []TestOption + wantOldTokenStatus TokenStatus + wantCount int + wantErr errors.Code + }{ + { + name: "valid", + wantOldTokenStatus: MaintainingToken, + wantCount: 1, + }, + { + name: "token-missing-capabilities", + newTokenOpts: []TestOption{WithPolicies([]string{"default"})}, + wantErr: errors.VaultTokenMissingCapabilities, + }, + { + name: "token-not-renewable", + newTokenOpts: []TestOption{TestRenewableToken(false)}, + wantErr: errors.VaultTokenNotRenewable, + }, + { + name: "token-not-orphan", + newTokenOpts: []TestOption{TestOrphanToken(false)}, + wantErr: errors.VaultTokenNotOrphan, + }, + { + name: "token-not-periodic", + newTokenOpts: []TestOption{TestPeriodicToken(false)}, + wantErr: errors.VaultTokenNotPeriodic, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t) + _, origToken := v.CreateToken(t) + + // create + origIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(origToken)) + assert.NoError(err) + require.NotNil(origIn) + + orig, err := repo.CreateCredentialStore(ctx, origIn) + assert.NoError(err) + require.NotNil(orig) + + // update + _, updateToken := v.CreateToken(t, tt.newTokenOpts...) + + updateIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(updateToken)) + assert.NoError(err) + require.NotNil(updateIn) + updateIn.PublicId = orig.GetPublicId() + got, gotCount, err := repo.UpdateCredentialStore(ctx, updateIn, 1, []string{"Token"}) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.Nil(got) + return + } + require.NoError(err) + assert.Equal(tt.wantCount, gotCount) + assert.NotNil(got) + + var tokens []*Token + require.NoError(rw.SearchWhere(ctx, &tokens, "store_id = ?", []interface{}{orig.GetPublicId()})) + assert.Len(tokens, 2) + assert.Equal(string(tt.wantOldTokenStatus), tokens[0].Status) + + lookup, err := repo.LookupCredentialStore(ctx, orig.GetPublicId()) + assert.NoError(err) + require.NotNil(lookup) + require.NotNil(orig.outputToken) + require.NotNil(got.outputToken) + assert.NotEqual(orig.outputToken.TokenHmac, got.outputToken.TokenHmac) + }) + } +} + +func TestRepository_UpdateCredentialStore_ClientCert(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + existingClientCert := func(t *testing.T, v *TestVaultServer) *ClientCertificate { + clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(t, err) + return clientCert + } + + newClientCert := func(t *testing.T, v *TestVaultServer) *ClientCertificate { + cb := testClientCert(t, v.clientCertBundle.CA) + return cb.Cert.ClientCertificate(t) + } + + // newClientCertFromServerCA is useful for when v is configured with + // TestServerTLS. It uses the server CA to create a new client + // certificate since the v will not have a clientCertBundle. + newClientCertFromServerCA := func(t *testing.T, v *TestVaultServer) *ClientCertificate { + cb := testClientCert(t, v.serverCertBundle.CA) + return cb.Cert.ClientCertificate(t) + } + + nilClientCert := func(t *testing.T, v *TestVaultServer) *ClientCertificate { + return nil + } + + assertUpdated := func(t *testing.T, org, updated *ClientCertificate, ps *privateStore) { + assert.Equal(t, updated.Certificate, ps.ClientCert, "updated certificate") + assert.Equal(t, updated.CertificateKey, []byte(ps.ClientKey), "updated certificate key") + } + + assertDeleted := func(t *testing.T, org, updated *ClientCertificate, ps *privateStore) { + assert.Nil(t, updated, "updated certificate") + assert.Nil(t, ps.ClientCert, "ps ClientCert") + assert.Nil(t, ps.ClientKey, "ps ClientKey") + assert.Nil(t, ps.CtClientKey, "ps CtClientKey") + } + + tests := []struct { + name string + tls TestVaultTLS + origFn func(t *testing.T, v *TestVaultServer) *ClientCertificate + updateFn func(t *testing.T, v *TestVaultServer) *ClientCertificate + wantFn func(t *testing.T, org, updated *ClientCertificate, ps *privateStore) + wantCount int + wantErr errors.Code + }{ + { + name: "ClientCert-to-ClientCert", + tls: TestClientTLS, + origFn: existingClientCert, + updateFn: newClientCert, + wantFn: assertUpdated, + wantCount: 1, + }, + { + name: "ClientCert-to-null", + tls: TestClientTLS, + origFn: existingClientCert, + updateFn: nilClientCert, + wantFn: assertDeleted, + wantCount: 1, + }, + { + name: "null-to-ClientCert", + tls: TestServerTLS, + origFn: nilClientCert, + updateFn: newClientCertFromServerCA, + wantFn: assertUpdated, + wantCount: 1, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + v := NewTestVaultServer(t, WithTestVaultTLS(tt.tls)) + + var opts []Option + if tt.tls == TestServerTLS { + opts = append(opts, WithCACert(v.CaCert)) + } + if tt.tls == TestClientTLS { + opts = append(opts, WithCACert(v.CaCert)) + clientCert, err := NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(err) + opts = append(opts, WithClientCert(clientCert)) + } + + _, origToken := v.CreateToken(t) + origClientCert := tt.origFn(t, v) + opts = append(opts, WithClientCert(origClientCert)) + + // create + origIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(origToken), opts...) + assert.NoError(err) + require.NotNil(origIn) + + orig, err := repo.CreateCredentialStore(ctx, origIn) + assert.NoError(err) + require.NotNil(orig) + + // update + updateClientCert := tt.updateFn(t, v) + + updateIn, err := NewCredentialStore(prj.GetPublicId(), v.Addr, []byte("ignore"), WithClientCert(updateClientCert)) + assert.NoError(err) + require.NotNil(updateIn) + updateIn.PublicId = orig.GetPublicId() + got, gotCount, err := repo.UpdateCredentialStore(ctx, updateIn, 1, []string{"Certificate", "CertificateKey"}) + assert.Equal(tt.wantCount, gotCount, "row count") + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + assert.NotNil(got) + + ps, err := repo.lookupPrivateStore(ctx, orig.GetPublicId()) + require.NoError(err) + tt.wantFn(t, origClientCert, updateClientCert, ps) + }) + } +} + +func TestRepository_ListCredentialStores_Multiple_Scopes(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + assert, require := assert.New(t), require.New(t) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + const numPerScope = 10 + var prjs []string + var total int + for i := 0; i < numPerScope; i++ { + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + prjs = append(prjs, prj.GetPublicId()) + TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), numPerScope) + total += numPerScope + } + + got, err := repo.ListCredentialStores(context.Background(), prjs) + require.NoError(err) + assert.Equal(total, len(got)) +} + +func TestRepository_DeleteCredentialStore(t *testing.T) { + type tokenCount struct { + current, maintaining int + revoke int + revoked, expired int + } + + type tokenMap map[string]*tokenCount + + type setupFn func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) + + baseSetup := func(t *testing.T, conn *gorm.DB) (wrapper wrapping.Wrapper, repo *Repository, scopeId string) { + wrapper = db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + repo, err := NewRepository(rw, rw, kms, sche) + require.NoError(t, err) + require.NotNil(t, repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + scopeId = prj.GetPublicId() + return + } + + testStores := func(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, tokens tokenMap, scopeId string, count int) ([]*CredentialStore, tokenMap) { + if tokens == nil { + tokens = make(tokenMap) + } + css := TestCredentialStores(t, conn, wrapper, scopeId, count) + for _, cs := range css { + storeId := cs.GetPublicId() + tokens[storeId] = new(tokenCount) + tokens[storeId].current = 1 + } + return css, tokens + } + + makeMaintainingTokens := func(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, tokens tokenMap, scopeId, storeId string, count int) tokenMap { + testTokens(t, conn, wrapper, scopeId, storeId, count) + tokens[storeId].maintaining = count + return tokens + } + + makeRevokedTokens := func(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, tokens tokenMap, scopeId, storeId string, count int) tokenMap { + require.NotNil(t, tokens) + const query = ` +update credential_vault_token + set status = 'revoked' + where store_id = $1 + and status = 'current'; +` + t.Helper() + rw := db.New(conn) + ctx := context.Background() + + for i := 0; i < count; i++ { + rows, err := rw.Exec(ctx, query, []interface{}{storeId}) + require.Equal(t, 1, rows) + require.NoError(t, err) + tokens[storeId].revoked++ + testTokens(t, conn, wrapper, scopeId, storeId, 1) + } + return tokens + } + + makeExpiredTokens := func(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, tokens tokenMap, scopeId, storeId string, count int) tokenMap { + require.NotNil(t, tokens) + const query = ` +update credential_vault_token + set status = 'expired' + where store_id = $1 + and status = 'current'; +` + t.Helper() + rw := db.New(conn) + ctx := context.Background() + + for i := 0; i < count; i++ { + rows, err := rw.Exec(ctx, query, []interface{}{storeId}) + require.Equal(t, 1, rows) + require.NoError(t, err) + tokens[storeId].expired++ + testTokens(t, conn, wrapper, scopeId, storeId, 1) + } + return tokens + } + + assertTokens := func(t *testing.T, conn *gorm.DB, want tokenMap) { + const query = ` + select store_id, status, count(token_hmac) + from credential_vault_token +group by store_id, status; +` + + rw := db.New(conn) + got := make(tokenMap, len(want)) + for id := range want { + got[id] = new(tokenCount) + } + + var ( + id string + status string + count int + ) + + ctx := context.Background() + rows, err := rw.Query(ctx, query, nil) + require.NoError(t, err) + defer rows.Close() + for rows.Next() { + require.NoError(t, rows.Scan(&id, &status, &count)) + switch status { + case "current": + got[id].current = count + case "maintaining": + got[id].maintaining = count + case "revoke": + got[id].revoke = count + case "revoked": + got[id].revoked = count + case "expired": + got[id].expired = count + default: + assert.Failf(t, "Unexpected status: %s", status) + } + } + require.NoError(t, rows.Err()) + assert.Equal(t, want, got) + } + + tests := []struct { + name string + setup setupFn + }{ + { + name: "simple", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + return + }, + }, + { + name: "with-libraries", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + + libs = TestCredentialLibraries(t, conn, wrapper, storeId, 4) + return + }, + }, + { + name: "with-maintaining-tokens", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + + libs = TestCredentialLibraries(t, conn, wrapper, storeId, 4) + tokens = makeMaintainingTokens(t, conn, wrapper, tokens, scopeId, storeId, 4) + return + }, + }, + { + name: "with-revoked-tokens", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + + libs = TestCredentialLibraries(t, conn, wrapper, storeId, 4) + tokens = makeRevokedTokens(t, conn, wrapper, tokens, scopeId, storeId, 4) + return + }, + }, + { + name: "with-expired-tokens", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + + libs = TestCredentialLibraries(t, conn, wrapper, storeId, 4) + tokens = makeExpiredTokens(t, conn, wrapper, tokens, scopeId, storeId, 4) + return + }, + }, + { + name: "with-all-token-statuses", + setup: func(t *testing.T, conn *gorm.DB) (storeId string, libs []*CredentialLibrary, tokens tokenMap, repo *Repository) { + wrapper, repo, scopeId := baseSetup(t, conn) + css, tokens := testStores(t, conn, wrapper, nil, scopeId, 1) + cs := css[0] + storeId = cs.GetPublicId() + + libs = TestCredentialLibraries(t, conn, wrapper, storeId, 4) + tokens = makeMaintainingTokens(t, conn, wrapper, tokens, scopeId, storeId, 2) + tokens = makeRevokedTokens(t, conn, wrapper, tokens, scopeId, storeId, 3) + tokens = makeExpiredTokens(t, conn, wrapper, tokens, scopeId, storeId, 5) + return + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + ctx := context.Background() + + storeId, actualLibs, tokens, repo := tt.setup(t, conn) + + var credStore *CredentialStore + var scopeId string + + assertTokens(t, conn, tokens) + { + lookup, err := repo.LookupCredentialStore(ctx, storeId) + assert.NoError(err) + require.NotNil(lookup) + assert.Nil(lookup.DeleteTime) + scopeId = lookup.GetScopeId() + credStore = lookup + } + + { + stores, err := repo.ListCredentialStores(ctx, []string{scopeId}) + assert.NoError(err) + assert.NotEmpty(stores) + var storeIds []string + for _, v := range stores { + storeIds = append(storeIds, v.GetPublicId()) + } + assert.Contains(storeIds, storeId) + } + + { + libs, err := repo.ListCredentialLibraries(ctx, storeId) + assert.NoError(err) + assert.Len(libs, len(actualLibs)) + } + + { + privStores, err := repo.listRevokePrivateStores(ctx) + assert.NoError(err) + assert.Empty(privStores) + } + + // verify updating the credential store works + { + masks := []string{"Name"} + credStore.Name = "test name" + updatedStore, updatedCount, err := repo.UpdateCredentialStore(ctx, credStore, credStore.Version, masks) + assert.NoError(err) + assert.Equal(1, updatedCount) + assert.NotNil(updatedStore) + credStore = updatedStore + } + + // delete + { + deletedCount, err := repo.DeleteCredentialStore(ctx, storeId) + assert.NoError(err) + assert.Equal(1, deletedCount) + } + + // All current and maintaining tokens should now be in the + // 'revoke' status. The status of any other tokens should be + // unchanged. + tokens[storeId].revoke = tokens[storeId].current + tokens[storeId].maintaining + tokens[storeId].current, tokens[storeId].maintaining = 0, 0 + assertTokens(t, conn, tokens) + + // should not be in lookup + { + lookup, err := repo.LookupCredentialStore(ctx, storeId) + assert.NoError(err) + assert.Nil(lookup) + } + + // should not be in list + { + stores, err := repo.ListCredentialStores(ctx, []string{scopeId}) + assert.NoError(err) + var storeIds []string + for _, v := range stores { + storeIds = append(storeIds, v.GetPublicId()) + } + assert.NotContains(storeIds, storeId) + } + + // libraries should be empty + { + libs, err := repo.ListCredentialLibraries(ctx, storeId) + assert.NoError(err) + assert.Empty(libs) + } + + // creating a library should fail + { + newLib := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + StoreId: storeId, + HttpMethod: "GET", + VaultPath: "/some/path", + }, + } + lib, err := repo.CreateCredentialLibrary(ctx, scopeId, newLib) + assert.Error(err) + assert.Nil(lib) + } + + var deleteTime *timestamp.Timestamp + // still in privateStore delete time set + { + privStores, err := repo.listRevokePrivateStores(ctx) + assert.NoError(err) + var privateStore *privateStore + var storeIds []string + for _, v := range privStores { + id := v.GetPublicId() + storeIds = append(storeIds, id) + if id == storeId { + privateStore = v + } + } + assert.Contains(storeIds, storeId) + require.NotNil(privateStore) + if assert.NotNil(privateStore.DeleteTime) { + deleteTime = privateStore.DeleteTime + } + } + + // updating a soft deleted credential store should not work + { + masks := []string{"Name"} + credStore.Name = "second test name" + updatedStore, updatedCount, err := repo.UpdateCredentialStore(ctx, credStore, credStore.Version, masks) + assert.Error(err) + assert.Equal(0, updatedCount) + assert.Nil(updatedStore) + } + + // calling delete again should not change anything + { + deleteCount, err := repo.DeleteCredentialStore(ctx, storeId) + assert.NoError(err) + assert.Equal(0, deleteCount) + } + + // still in privateStore delete time should not change + { + privStores, err := repo.listRevokePrivateStores(ctx) + assert.NoError(err) + var privateStore *privateStore + var storeIds []string + for _, v := range privStores { + id := v.GetPublicId() + storeIds = append(storeIds, id) + if id == storeId { + privateStore = v + } + } + assert.Contains(storeIds, storeId) + require.NotNil(privateStore) + assert.Empty(cmp.Diff(deleteTime, privateStore.DeleteTime, protocmp.Transform())) + } + }) + } +} diff --git a/internal/credential/vault/repository_credentials.go b/internal/credential/vault/repository_credentials.go new file mode 100644 index 000000000..6bd18454a --- /dev/null +++ b/internal/credential/vault/repository_credentials.go @@ -0,0 +1,140 @@ +package vault + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + vault "github.com/hashicorp/vault/api" +) + +var _ credential.Issuer = (*Repository)(nil) + +// Issue issues and returns dynamic credentials from Vault for all of the +// requests and assigns them to sessionId. +func (r *Repository) Issue(ctx context.Context, sessionId string, requests []credential.Request) ([]credential.Dynamic, error) { + const op = "vault.(Repository).Issue" + if sessionId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no session id") + } + if len(requests) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no requests") + } + + libs, err := r.getPrivateLibraries(ctx, requests) + if err != nil { + return nil, errors.Wrap(err, op) + } + + // TODO(mgaffney)(ICU-1329) 05/2021: if any error occurs, mark all credentials + // retrieved for revocation which will be handled by the revocation + // job. + + var creds []credential.Dynamic + var minLease time.Duration + for _, lib := range libs { + // Get the credential ID early. No need to get a secret from Vault + // if there is no way to save it in the database. + credId, err := newCredentialId() + if err != nil { + return nil, errors.Wrap(err, op) + } + + client, err := lib.client() + if err != nil { + return nil, errors.Wrap(err, op) + } + + var secret *vault.Secret + switch Method(lib.HttpMethod) { + case MethodGet: + secret, err = client.get(lib.VaultPath) + case MethodPost: + secret, err = client.post(lib.VaultPath, lib.HttpRequestBody) + default: + return nil, errors.New(errors.Internal, op, fmt.Sprintf("unknown http method: library: %s", lib.PublicId)) + } + + if err != nil { + // TODO(mgaffney) 05/2021: detect if the error is because of an + // expired or invalid token + return nil, errors.Wrap(err, op) + } + + leaseDuration := time.Duration(secret.LeaseDuration) * time.Second + if minLease > leaseDuration { + minLease = leaseDuration + } + cred, err := newCredential(lib.GetPublicId(), sessionId, secret.LeaseID, lib.TokenHmac, leaseDuration) + if err != nil { + return nil, errors.Wrap(err, op) + } + cred.PublicId = credId + cred.IsRenewable = secret.Renewable + + insertQuery, insertQueryValues := cred.insertQuery() + updateQuery, updateQueryValues := cred.updateSessionQuery(lib.Purpose) + if _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + rowsInserted, err := w.Exec(ctx, insertQuery, insertQueryValues) + switch { + case err != nil: + return errors.Wrap(err, op) + case rowsInserted > 1: + return errors.New(errors.MultipleRecords, op, "more than 1 credential would have been inserted") + } + + rowsUpdated, err := w.Exec(ctx, updateQuery, updateQueryValues) + switch { + case err != nil: + return errors.Wrap(err, op) + case rowsUpdated == 0: + return errors.New(errors.InvalidDynamicCredential, op, "no matching dynamic credential for session found") + case rowsUpdated > 1: + return errors.New(errors.MultipleRecords, op, "more than 1 session credential would have been updated") + } + return nil + }, + ); err != nil { + return nil, errors.Wrap(err, op) + } + + creds = append(creds, &actualCredential{ + id: cred.PublicId, + sessionId: cred.SessionId, + lib: lib, + secretData: secret.Data, + purpose: lib.Purpose, + }) + } + + // Best effort update next run time of credential renewal job, but an error should not + // cause Issue to fail. + // TODO (lcr 06/2021): log error once repo has logger + _ = r.scheduler.UpdateJobNextRunInAtLeast(ctx, credentialRenewalJobName, minLease) + + return creds, nil +} + +var _ credential.Revoker = (*Repository)(nil) + +// Revoke revokes all dynamic credentials issued from Vault for sessionId. +func (r *Repository) Revoke(ctx context.Context, sessionId string) error { + const op = "vault.(Repository).Revoke" + if sessionId == "" { + return errors.New(errors.InvalidParameter, op, "no session id") + } + + _, err := r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + if _, err := w.Exec(ctx, revokeCredentialsQuery, []interface{}{sessionId}); err != nil { + return errors.Wrap(err, op) + } + return nil + }, + ) + return err +} diff --git a/internal/credential/vault/repository_credentials_test.go b/internal/credential/vault/repository_credentials_test.go new file mode 100644 index 000000000..4432b602d --- /dev/null +++ b/internal/credential/vault/repository_credentials_test.go @@ -0,0 +1,466 @@ +package vault_test + +import ( + "context" + "path" + "testing" + + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/boundary/internal/session" + "github.com/hashicorp/boundary/internal/target" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_IssueCredentials(t *testing.T) { + t.Parallel() + ctx := context.Background() + + v := vault.NewTestVaultServer(t, vault.WithDockerNetwork(true), vault.WithTestVaultTLS(vault.TestClientTLS)) + v.MountDatabase(t) + v.MountPKI(t) + + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + kms := kms.TestKms(t, conn, wrapper) + + assert, require := assert.New(t), require.New(t) + + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := vault.NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + + _, token := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "database", "pki"})) + + var opts []vault.Option + opts = append(opts, vault.WithCACert(v.CaCert)) + clientCert, err := vault.NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(err) + opts = append(opts, vault.WithClientCert(clientCert)) + + credStoreIn, err := vault.NewCredentialStore(prj.GetPublicId(), v.Addr, []byte(token), opts...) + assert.NoError(err) + require.NotNil(credStoreIn) + origStore, err := repo.CreateCredentialStore(ctx, credStoreIn) + assert.NoError(err) + require.NotNil(origStore) + + type libT int + const ( + libDB libT = iota + libPKI + libErrPKI + ) + + libs := make(map[libT]string) + { + libPath := path.Join("database", "creds", "opened") + libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath) + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[libDB] = lib.GetPublicId() + } + { + libPath := path.Join("pki", "issue", "boundary") + libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, vault.WithMethod(vault.MethodPost), vault.WithRequestBody([]byte(`{"common_name":"boundary.com"}`))) + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[libPKI] = lib.GetPublicId() + } + { + + libPath := path.Join("pki", "issue", "boundary") + libIn, err := vault.NewCredentialLibrary(origStore.GetPublicId(), libPath, vault.WithMethod(vault.MethodPost)) + assert.NoError(err) + require.NotNil(libIn) + lib, err := repo.CreateCredentialLibrary(ctx, prj.GetPublicId(), libIn) + assert.NoError(err) + require.NotNil(lib) + libs[libErrPKI] = lib.GetPublicId() + } + + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + uId := at.GetIamUserId() + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + + rc2dc := func(rcs []credential.Request) []*session.DynamicCredential { + var dcs []*session.DynamicCredential + for _, rc := range rcs { + dc := &session.DynamicCredential{ + LibraryId: rc.SourceId, + CredentialPurpose: string(rc.Purpose), + } + dcs = append(dcs, dc) + } + return dcs + } + + rc2nil := func(rcs []credential.Request) []*session.DynamicCredential { return nil } + + tests := []struct { + name string + convertFn func(rcs []credential.Request) []*session.DynamicCredential + requests []credential.Request + wantErr errors.Code + }{ + { + name: "one-library-valid", + convertFn: rc2dc, + requests: []credential.Request{ + { + SourceId: libs[libDB], + Purpose: credential.ApplicationPurpose, + }, + }, + }, + { + name: "multiple-valid-libraries", + convertFn: rc2dc, + requests: []credential.Request{ + { + SourceId: libs[libDB], + Purpose: credential.ApplicationPurpose, + }, + { + SourceId: libs[libPKI], + Purpose: credential.IngressPurpose, + }, + }, + }, + { + name: "one-library-that-errors", + convertFn: rc2dc, + requests: []credential.Request{ + { + SourceId: libs[libErrPKI], + Purpose: credential.IngressPurpose, + }, + }, + wantErr: errors.VaultCredentialRequest, + }, + { + name: "no-session-dynamic-credentials", + convertFn: rc2nil, + requests: []credential.Request{ + { + SourceId: libs[libDB], + Purpose: credential.ApplicationPurpose, + }, + { + SourceId: libs[libPKI], + Purpose: credential.IngressPurpose, + }, + }, + wantErr: errors.InvalidDynamicCredential, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + DynamicCredentials: tt.convertFn(tt.requests), + }) + got, err := repo.Issue(ctx, sess.GetPublicId(), tt.requests) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + assert.Len(got, len(tt.requests)) + assert.NoError(err) + }) + } +} + +func TestRepository_Revoke(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + cl := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0] + + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + + const ( + sessionCount = 4 + credentialCount = 4 + ) + + sessionIds := make([]string, sessionCount) + sessions := make(map[string][]*vault.Credential, sessionCount) + for i := 0; i < sessionCount; i++ { + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + uId := at.GetIamUserId() + + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + sessionIds[i] = sess.GetPublicId() + credentials := vault.TestCredentials(t, conn, wrapper, cl.GetPublicId(), sess.GetPublicId(), credentialCount) + assert.Len(credentials, credentialCount) + for _, credential := range credentials { + assert.NotEmpty(credential.GetPublicId()) + } + sessions[sess.GetPublicId()] = credentials + } + + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := vault.NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + + ctx := context.Background() + assert.Error(repo.Revoke(ctx, "")) + + type credCount struct { + active, revoke int + revoked, expired, unknown int + } + + assertCreds := func(want map[string]*credCount) { + const query = ` + select session_id, status, count(public_id) + from credential_vault_credential +group by session_id, status; +` + got := make(map[string]*credCount, len(want)) + for id := range want { + got[id] = new(credCount) + } + + var ( + id string + status string + count int + ) + rows, err := rw.Query(ctx, query, nil) + require.NoError(err) + defer rows.Close() + for rows.Next() { + require.NoError(rows.Scan(&id, &status, &count)) + switch status { + case "active": + got[id].active = count + case "revoke": + got[id].revoke = count + case "revoked": + got[id].revoked = count + case "expired": + got[id].expired = count + case "unknown": + got[id].unknown = count + default: + assert.Failf("Unexpected status: %s", status) + } + } + require.NoError(rows.Err()) + assert.Equal(want, got) + } + + cc := make(map[string]*credCount, sessionCount) + for _, id := range sessionIds { + cc[id] = new(credCount) + cc[id].active = credentialCount + } + for _, id := range sessionIds { + assert.NoError(repo.Revoke(ctx, id)) + cc[id].revoke = cc[id].active + cc[id].active = 0 + assertCreds(cc) + } +} + +func Test_TerminateSession(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + cl := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0] + + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + + const ( + sessionCount = 4 + credentialCount = 4 + ) + + sessionIds := make([]string, sessionCount) + sessions := make(map[string][]*vault.Credential, sessionCount) + for i := 0; i < sessionCount; i++ { + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + uId := at.GetIamUserId() + + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + sessionIds[i] = sess.GetPublicId() + credentials := vault.TestCredentials(t, conn, wrapper, cl.GetPublicId(), sess.GetPublicId(), credentialCount) + assert.Len(credentials, credentialCount) + for _, credential := range credentials { + assert.NotEmpty(credential.GetPublicId()) + } + sessions[sess.GetPublicId()] = credentials + } + + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := vault.NewRepository(rw, rw, kms, sche) + require.NoError(err) + require.NotNil(repo) + + ctx := context.Background() + assert.Error(repo.Revoke(ctx, "")) + + type credCount struct { + active, revoke int + revoked, expired, unknown int + } + + assertCreds := func(want map[string]*credCount) { + const query = ` + select session_id, status, count(public_id) + from credential_vault_credential +group by session_id, status; +` + got := make(map[string]*credCount, len(want)) + for id := range want { + got[id] = new(credCount) + } + + var ( + id string + status string + count int + ) + rows, err := rw.Query(ctx, query, nil) + require.NoError(err) + defer rows.Close() + for rows.Next() { + require.NoError(rows.Scan(&id, &status, &count)) + switch status { + case "active": + got[id].active = count + case "revoke": + got[id].revoke = count + case "revoked": + got[id].revoked = count + case "expired": + got[id].expired = count + case "unknown": + got[id].unknown = count + default: + assert.Failf("Unexpected status: %s", status) + } + } + require.NoError(rows.Err()) + assert.Equal(want, got) + } + + cc := make(map[string]*credCount, sessionCount) + for _, id := range sessionIds { + cc[id] = new(credCount) + cc[id].active = credentialCount + } + + sessionRepo, err := session.NewRepository(rw, rw, kms) + require.NoError(err) + + count, err := sessionRepo.TerminateCompletedSessions(ctx) + assert.NoError(err) + assert.Zero(count) + assertCreds(cc) + + // call CancelSession + id := sessionIds[0] + _, err = sessionRepo.CancelSession(ctx, id, 1) + assert.NoError(err) + cc[id].revoke = cc[id].active + cc[id].active = 0 + assertCreds(cc) + + count, err = sessionRepo.TerminateCompletedSessions(ctx) + assert.NoError(err) + assert.Equal(1, count) + assertCreds(cc) + + // call TerminateSession + id = sessionIds[1] + _, err = sessionRepo.TerminateSession(ctx, id, 1, session.ClosedByUser) + assert.NoError(err) + cc[id].revoke = cc[id].active + cc[id].active = 0 + assertCreds(cc) + + count, err = sessionRepo.TerminateCompletedSessions(ctx) + assert.NoError(err) + assert.Zero(count) + assertCreds(cc) +} diff --git a/internal/credential/vault/repository_test.go b/internal/credential/vault/repository_test.go new file mode 100644 index 000000000..e3d84e131 --- /dev/null +++ b/internal/credential/vault/repository_test.go @@ -0,0 +1,140 @@ +package vault + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + scheduler *scheduler.Scheduler + opts []Option + } + + tests := []struct { + name string + args args + want *Repository + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: sche, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + scheduler: sche, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: sche, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + scheduler: sche, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + scheduler: sche, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + scheduler: sche, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-kms", + args: args{ + r: rw, + w: rw, + kms: nil, + scheduler: sche, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-scheduler", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + scheduler: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: nil, + scheduler: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewRepository(tt.args.r, tt.args.w, tt.args.kms, tt.args.scheduler, tt.args.opts...) + if tt.wantIsErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "want err: %q got: %q", tt.wantIsErr, err) + assert.Nil(got) + return + } + assert.NoError(err) + require.NotNil(got) + assert.Equal(tt.want, got) + }) + } +} diff --git a/internal/credential/vault/secret.go b/internal/credential/vault/secret.go new file mode 100644 index 000000000..99be128ae --- /dev/null +++ b/internal/credential/vault/secret.go @@ -0,0 +1,49 @@ +package vault + +import ( + "encoding/json" +) + +// TokenSecret equals a Vault token. This type provides a +// wrapper so the secret isn't inadvertently leaked into a log or error. +type TokenSecret []byte + +// redactedTokenSecret is the redacted string or json for an Vault token. +const redactedTokenSecret = "[REDACTED: Vault token_secret]" + +// String will redact the TokenSecret. +func (s TokenSecret) String() string { + return redactedTokenSecret +} + +// GoString will redact the TokenSecret. +func (s TokenSecret) GoString() string { + return redactedTokenSecret +} + +// MarshalJSON will redact the TokenSecret. +func (s TokenSecret) MarshalJSON() ([]byte, error) { + return json.Marshal([]byte(redactedTokenSecret)) +} + +// KeySecret equals a Vault client certificate private key. This type provides a +// wrapper so the secret isn't inadvertently leaked into a log or error. +type KeySecret []byte + +// redactedKeySecret is the redacted string or json for an Vault client certificate key. +const redactedKeySecret = "[REDACTED: Vault key_secret]" + +// String will redact the TokenSecret. +func (s KeySecret) String() string { + return redactedKeySecret +} + +// GoString will redact the TokenSecret. +func (s KeySecret) GoString() string { + return redactedKeySecret +} + +// MarshalJSON will redact the TokenSecret. +func (s KeySecret) MarshalJSON() ([]byte, error) { + return json.Marshal([]byte(redactedKeySecret)) +} diff --git a/internal/credential/vault/secret_test.go b/internal/credential/vault/secret_test.go new file mode 100644 index 000000000..2dd11211e --- /dev/null +++ b/internal/credential/vault/secret_test.go @@ -0,0 +1,132 @@ +package vault + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTokenSecret_String(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert := assert.New(t) + const want = redactedTokenSecret + tk := TokenSecret("special secret") + assert.Equalf(want, tk.String(), "TokenSecret.String() = %v, want %v", tk.String(), want) + + // Verify stringer is called + s := fmt.Sprintf("%s", tk) + assert.Equalf(want, s, "TokenSecret.String() = %v, want %v", s, want) + }) +} + +func TestTokenSecret_GoString(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert := assert.New(t) + const want = redactedTokenSecret + tk := TokenSecret("magic secret") + assert.Equalf(want, tk.GoString(), "TokenSecret.GoString() = %v, want %v", tk.GoString(), want) + + // Verify gostringer is called + s := fmt.Sprintf("%#v", tk) + assert.Equalf(want, s, "TokenSecret.GoString() = %v, want %v", s, want) + }) +} + +func TestTokenSecret_MarshalJSON(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + want, err := json.Marshal([]byte(redactedTokenSecret)) + require.NoError(err) + tk := TokenSecret("normal secret") + got, err := tk.MarshalJSON() + require.NoError(err) + assert.Equalf(want, got, "TokenSecret.MarshalJSON() = %s, want %s", got, want) + }) + t.Run("within-struct", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + want := fmt.Sprintf(`%s`, redactedTokenSecret) + + type secretContainer struct { + S TokenSecret + B []byte + } + testB := []byte("my secret") + secret := secretContainer{S: testB, B: testB} + + m, err := json.Marshal(secret) + require.NoError(err) + + var sec secretContainer + err = json.Unmarshal(m, &sec) + require.NoError(err) + assert.Equal(TokenSecret(want), sec.S) + assert.Equal(testB, sec.B) + }) +} + +func TestKeySecret_String(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert := assert.New(t) + const want = redactedKeySecret + tk := KeySecret("our secret") + assert.Equalf(want, tk.String(), "KeySecret.String() = %v, want %v", tk.String(), want) + + // Verify stringer is called + s := fmt.Sprintf("%s", tk) + assert.Equalf(want, s, "KeySecret.String() = %v, want %v", s, want) + }) +} + +func TestKeySecret_GoString(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert := assert.New(t) + const want = redactedKeySecret + tk := KeySecret("private secret") + assert.Equalf(want, tk.GoString(), "KeySecret.GoString() = %v, want %v", tk.GoString(), want) + + // Verify gostringer is called + s := fmt.Sprintf("%#v", tk) + assert.Equalf(want, s, "KeySecret.GoString() = %v, want %v", s, want) + }) +} + +func TestKeySecret_MarshalJSON(t *testing.T) { + t.Parallel() + t.Run("redacted", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + want, err := json.Marshal([]byte(redactedKeySecret)) + require.NoError(err) + tk := KeySecret("hidden secret") + got, err := tk.MarshalJSON() + require.NoError(err) + assert.Equalf(want, got, "KeySecret.MarshalJSON() = %s, want %s", got, want) + }) + t.Run("within-struct", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + want := fmt.Sprintf(`%s`, redactedKeySecret) + + type secretContainer struct { + S KeySecret + B []byte + } + testB := []byte("secure secret") + secret := secretContainer{S: testB, B: testB} + + m, err := json.Marshal(secret) + require.NoError(err) + + var sec secretContainer + err = json.Unmarshal(m, &sec) + require.NoError(err) + assert.Equal(KeySecret(want), sec.S) + assert.Equal(testB, sec.B) + }) +} diff --git a/internal/credential/vault/store/vault.pb.go b/internal/credential/vault/store/vault.pb.go new file mode 100644 index 000000000..a1919d244 --- /dev/null +++ b/internal/credential/vault/store/vault.pb.go @@ -0,0 +1,1108 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/storage/credential/vault/store/v1/vault.proto + +// Package store provides protobufs for storing types in the vault +// credential package. + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/internal/gen/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type CredentialStore struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // The delete_time is set by the application. + // @inject_tag: `gorm:"default:null"` + DeleteTime *timestamp.Timestamp `protobuf:"bytes,4,opt,name=delete_time,json=deleteTime,proto3" json:"delete_time,omitempty" gorm:"default:null"` + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // The scope_id of the owning scope. + // It must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,7,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"not_null"` + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,8,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // vault_address is the address of the vault server. This should be a + // complete URL. + // It must be set. + // @inject_tag: `gorm:"not_null"` + VaultAddress string `protobuf:"bytes,9,opt,name=vault_address,json=vaultAddress,proto3" json:"vault_address,omitempty" gorm:"not_null"` + // namespace is the vault namespace the credential store should use. + // It is optional. + // @inject_tag: `gorm:"default:null"` + Namespace string `protobuf:"bytes,10,opt,name=namespace,proto3" json:"namespace,omitempty" gorm:"default:null"` + // ca_cert is a single PEM-encoded CA certificate to verify the Vault + // server's SSL certificate. + // It is optional. + // @inject_tag: `gorm:"default:null"` + CaCert []byte `protobuf:"bytes,11,opt,name=ca_cert,json=caCert,proto3" json:"ca_cert,omitempty" gorm:"default:null"` + // tls_server_name is the name to use as the SNI host when connecting to + // Vault via TLS. + // It is optional. + // @inject_tag: `gorm:"default:null"` + TlsServerName string `protobuf:"bytes,12,opt,name=tls_server_name,json=tlsServerName,proto3" json:"tls_server_name,omitempty" gorm:"default:null"` + // tls_skip_verify disables verification of TLS certificates. Using this + // option is highly discouraged as it decreases the security of data + // transmissions to and from the Vault server. + // @inject_tag: `gorm:"default:false"` + TlsSkipVerify bool `protobuf:"varint,13,opt,name=tls_skip_verify,json=tlsSkipVerify,proto3" json:"tls_skip_verify,omitempty" gorm:"default:false"` +} + +func (x *CredentialStore) Reset() { + *x = CredentialStore{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialStore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialStore) ProtoMessage() {} + +func (x *CredentialStore) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[0] + 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 CredentialStore.ProtoReflect.Descriptor instead. +func (*CredentialStore) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{0} +} + +func (x *CredentialStore) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *CredentialStore) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *CredentialStore) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *CredentialStore) GetDeleteTime() *timestamp.Timestamp { + if x != nil { + return x.DeleteTime + } + return nil +} + +func (x *CredentialStore) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CredentialStore) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CredentialStore) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *CredentialStore) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *CredentialStore) GetVaultAddress() string { + if x != nil { + return x.VaultAddress + } + return "" +} + +func (x *CredentialStore) GetNamespace() string { + if x != nil { + return x.Namespace + } + return "" +} + +func (x *CredentialStore) GetCaCert() []byte { + if x != nil { + return x.CaCert + } + return nil +} + +func (x *CredentialStore) GetTlsServerName() string { + if x != nil { + return x.TlsServerName + } + return "" +} + +func (x *CredentialStore) GetTlsSkipVerify() bool { + if x != nil { + return x.TlsSkipVerify + } + return false +} + +type Token struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // token_hmac is the hmac-sha256 value of the token. The hmac key is the + // blake2b-256 value of the token accessor. + // @inject_tag: `gorm:"primary_key"` + TokenHmac []byte `protobuf:"bytes,1,opt,name=token_hmac,json=tokenHmac,proto3" json:"token_hmac,omitempty" gorm:"primary_key"` + // token is the plain-text of the token data. We are not storing this + // plain-text token in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,token_data"` + Token []byte `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty" gorm:"-" wrapping:"pt,token_data"` + // ct_token is the ciphertext of the token data. It is stored in the database. + // @inject_tag: `gorm:"column:token;not_null" wrapping:"ct,token_data"` + CtToken []byte `protobuf:"bytes,3,opt,name=ct_token,json=ctToken,proto3" json:"ct_token,omitempty" gorm:"column:token;not_null" wrapping:"ct,token_data"` + // store_id is the ID of the owning vault credential store. A vault + // credential store can have 0 or 1 token. + // It must be set. + // @inject_tag: `gorm:"not_null"` + StoreId string `protobuf:"bytes,4,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"` + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,6,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // last_renewal_time is the time the token was last renewed with Vault. + // @inject_tag: `gorm:"default:null"` + LastRenewalTime *timestamp.Timestamp `protobuf:"bytes,8,opt,name=last_renewal_time,json=lastRenewalTime,proto3" json:"last_renewal_time,omitempty" gorm:"default:null"` + // expiration_time is the time the token is expected to expire. + // expiration_time is calculated when the token is renewed. + // + // The calculation is: + // expiration_time := time.Now().Add(LeaseDuration * time.Second) + // LeaseDuration is a value returned by Vault when the token is renewed. + // + // https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self + // + // @inject_tag: `gorm:"default:null"` + ExpirationTime *timestamp.Timestamp `protobuf:"bytes,9,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty" gorm:"default:null"` + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,10,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` + // The status of the token. + // It must be set. + // @inject_tag: `gorm:"not_null"` + Status string `protobuf:"bytes,11,opt,name=status,proto3" json:"status,omitempty" gorm:"not_null"` +} + +func (x *Token) Reset() { + *x = Token{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Token) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Token) ProtoMessage() {} + +func (x *Token) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[1] + 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 Token.ProtoReflect.Descriptor instead. +func (*Token) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{1} +} + +func (x *Token) GetTokenHmac() []byte { + if x != nil { + return x.TokenHmac + } + return nil +} + +func (x *Token) GetToken() []byte { + if x != nil { + return x.Token + } + return nil +} + +func (x *Token) GetCtToken() []byte { + if x != nil { + return x.CtToken + } + return nil +} + +func (x *Token) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +func (x *Token) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Token) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Token) GetLastRenewalTime() *timestamp.Timestamp { + if x != nil { + return x.LastRenewalTime + } + return nil +} + +func (x *Token) GetExpirationTime() *timestamp.Timestamp { + if x != nil { + return x.ExpirationTime + } + return nil +} + +func (x *Token) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +func (x *Token) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +type ClientCertificate struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // store_id is the ID of the owning vault credential store. A vault + // credential store can have 0 or 1 client certificate. + // @inject_tag: `gorm:"primary_key"` + StoreId string `protobuf:"bytes,1,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"primary_key"` + // certificate is the PEM encoded certificate. + // It must be set. + // @inject_tag: `gorm:"not_null"` + Certificate []byte `protobuf:"bytes,2,opt,name=certificate,proto3" json:"certificate,omitempty" gorm:"not_null"` + // certificate_key is the plain-text of the certificate key data. We are + // not storing this plain-text key in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,certificate_key_data"` + CertificateKey []byte `protobuf:"bytes,3,opt,name=certificate_key,json=certificateKey,proto3" json:"certificate_key,omitempty" gorm:"-" wrapping:"pt,certificate_key_data"` + // ct_certificate_key is the ciphertext of the certificate key data. It + // is stored in the database. + // @inject_tag: `gorm:"column:certificate_key;not_null" wrapping:"ct,certificate_key_data"` + CtCertificateKey []byte `protobuf:"bytes,4,opt,name=ct_certificate_key,json=ctCertificateKey,proto3" json:"ct_certificate_key,omitempty" gorm:"column:certificate_key;not_null" wrapping:"ct,certificate_key_data"` + // certificate_key_hmac is a sha256-hmac of the unencrypted certificate_key that + // is returned from the API for read. It is recalculated everytime the raw + // certificate_key is updated. + // @inject_tag: `gorm:"not_null"` + CertificateKeyHmac []byte `protobuf:"bytes,130,opt,name=certificate_key_hmac,json=certificateKeyHmac,proto3" json:"certificate_key_hmac,omitempty" gorm:"not_null"` + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + KeyId string `protobuf:"bytes,10,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` +} + +func (x *ClientCertificate) Reset() { + *x = ClientCertificate{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ClientCertificate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ClientCertificate) ProtoMessage() {} + +func (x *ClientCertificate) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_vault_store_v1_vault_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 ClientCertificate.ProtoReflect.Descriptor instead. +func (*ClientCertificate) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{2} +} + +func (x *ClientCertificate) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +func (x *ClientCertificate) GetCertificate() []byte { + if x != nil { + return x.Certificate + } + return nil +} + +func (x *ClientCertificate) GetCertificateKey() []byte { + if x != nil { + return x.CertificateKey + } + return nil +} + +func (x *ClientCertificate) GetCtCertificateKey() []byte { + if x != nil { + return x.CtCertificateKey + } + return nil +} + +func (x *ClientCertificate) GetCertificateKeyHmac() []byte { + if x != nil { + return x.CertificateKeyHmac + } + return nil +} + +func (x *ClientCertificate) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +type CredentialLibrary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,2,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,3,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description is optional. + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // store_id of the owning vault credential store. + // It must be set. + // @inject_tag: `gorm:"not_null"` + StoreId string `protobuf:"bytes,6,opt,name=store_id,json=storeId,proto3" json:"store_id,omitempty" gorm:"not_null"` + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // vault_path is the path in Vault to request credentials from. + // It must be set. + // @inject_tag: `gorm:"not_null"` + VaultPath string `protobuf:"bytes,8,opt,name=vault_path,json=vaultPath,proto3" json:"vault_path,omitempty" gorm:"not_null"` + // The HTTP method the library uses to communicate with Vault. + // It must be set. Can only be GET or POST. + // @inject_tag: `gorm:"not_null"` + HttpMethod string `protobuf:"bytes,9,opt,name=http_method,json=httpMethod,proto3" json:"http_method,omitempty" gorm:"not_null"` + // The body of the HTTP request the library sends to Vault. + // Can only be set if http_method is POST. + // @inject_tag: `gorm:"default:null"` + HttpRequestBody []byte `protobuf:"bytes,10,opt,name=http_request_body,json=httpRequestBody,proto3" json:"http_request_body,omitempty" gorm:"default:null"` +} + +func (x *CredentialLibrary) Reset() { + *x = CredentialLibrary{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialLibrary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialLibrary) ProtoMessage() {} + +func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[3] + 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 CredentialLibrary.ProtoReflect.Descriptor instead. +func (*CredentialLibrary) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{3} +} + +func (x *CredentialLibrary) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *CredentialLibrary) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *CredentialLibrary) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *CredentialLibrary) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CredentialLibrary) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CredentialLibrary) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +func (x *CredentialLibrary) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *CredentialLibrary) GetVaultPath() string { + if x != nil { + return x.VaultPath + } + return "" +} + +func (x *CredentialLibrary) GetHttpMethod() string { + if x != nil { + return x.HttpMethod + } + return "" +} + +func (x *CredentialLibrary) GetHttpRequestBody() []byte { + if x != nil { + return x.HttpRequestBody + } + return nil +} + +type Credential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + PublicId string `protobuf:"bytes,1,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // library_id of the owning vault credential library. + // It must be set. + LibraryId string `protobuf:"bytes,2,opt,name=library_id,json=libraryId,proto3" json:"library_id,omitempty"` + // session_id of the session the credential was created for. + // It must be set. + SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + // token_hmac is the foreign key of the token used to acquire the secret. + // @inject_tag: `gorm:"not_null"` + TokenHmac []byte `protobuf:"bytes,4,opt,name=token_hmac,json=tokenHmac,proto3" json:"token_hmac,omitempty" gorm:"not_null"` + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,5,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,6,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // external_id is the lease ID returned from Vault. + // @inject_tag: `gorm:"not_null"` + ExternalId string `protobuf:"bytes,8,opt,name=external_id,json=externalId,proto3" json:"external_id,omitempty" gorm:"not_null"` + // last_renewal_time is the time the lease was last renewed with Vault. + // @inject_tag: `gorm:"default:null"` + LastRenewalTime *timestamp.Timestamp `protobuf:"bytes,9,opt,name=last_renewal_time,json=lastRenewalTime,proto3" json:"last_renewal_time,omitempty" gorm:"default:null"` + // expiration_time is the time the lease is expected to expire. + // expiration_time is calculated when the credential is initially + // retrieved and whenever the credential's lease is renewed. + // + // The calculation is: + // expiration_time := time.Now().Add(LeaseDuration * time.Second) + // LeaseDuration is a value returned by Vault when the credential is + // retrieved or the lease for the credential is renewed. + // + // https://www.vaultproject.io/docs/concepts/lease + // + // @inject_tag: `gorm:"default:null"` + ExpirationTime *timestamp.Timestamp `protobuf:"bytes,10,opt,name=expiration_time,json=expirationTime,proto3" json:"expiration_time,omitempty" gorm:"default:null"` + // is_renewable indicates if the lease can be renewed. + // @inject_tag: `gorm:"default:false"` + IsRenewable bool `protobuf:"varint,11,opt,name=is_renewable,json=isRenewable,proto3" json:"is_renewable,omitempty" gorm:"default:false"` + // The status of the credential. + // It must be set. + // @inject_tag: `gorm:"not_null"` + Status string `protobuf:"bytes,12,opt,name=status,proto3" json:"status,omitempty" gorm:"not_null"` +} + +func (x *Credential) Reset() { + *x = Credential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Credential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Credential) ProtoMessage() {} + +func (x *Credential) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4] + 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 Credential.ProtoReflect.Descriptor instead. +func (*Credential) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP(), []int{4} +} + +func (x *Credential) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Credential) GetLibraryId() string { + if x != nil { + return x.LibraryId + } + return "" +} + +func (x *Credential) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *Credential) GetTokenHmac() []byte { + if x != nil { + return x.TokenHmac + } + return nil +} + +func (x *Credential) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Credential) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Credential) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Credential) GetExternalId() string { + if x != nil { + return x.ExternalId + } + return "" +} + +func (x *Credential) GetLastRenewalTime() *timestamp.Timestamp { + if x != nil { + return x.LastRenewalTime + } + return nil +} + +func (x *Credential) GetExpirationTime() *timestamp.Timestamp { + if x != nil { + return x.ExpirationTime + } + return nil +} + +func (x *Credential) GetIsRenewable() bool { + if x != nil { + return x.IsRenewable + } + return false +} + +func (x *Credential) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +var File_controller_storage_credential_vault_store_v1_vault_proto protoreflect.FileDescriptor + +var file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = []byte{ + 0x0a, 0x38, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2f, + 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x76, + 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2c, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb1, 0x06, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, + 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x24, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, + 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, + 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0d, 0x76, 0x61, + 0x75, 0x6c, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x26, 0xc2, 0xdd, 0x29, 0x22, 0x0a, 0x0c, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x0c, 0x76, 0x61, 0x75, 0x6c, 0x74, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x43, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x42, 0x25, 0xc2, 0xdd, 0x29, 0x21, + 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x39, 0x0a, 0x07, + 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x20, 0xc2, + 0xdd, 0x29, 0x1c, 0x0a, 0x06, 0x43, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x12, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x52, + 0x06, 0x63, 0x61, 0x43, 0x65, 0x72, 0x74, 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x6c, 0x73, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x2f, 0xc2, 0xdd, 0x29, 0x2b, 0x0a, 0x0d, 0x54, 0x6c, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, + 0x65, 0x52, 0x0d, 0x74, 0x6c, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, + 0x12, 0x57, 0x0a, 0x0f, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, + 0x69, 0x66, 0x79, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x08, 0x42, 0x2f, 0xc2, 0xdd, 0x29, 0x2b, 0x0a, + 0x0d, 0x54, 0x6c, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x1a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x5f, 0x73, + 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x52, 0x0d, 0x74, 0x6c, 0x73, 0x53, + 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x22, 0x87, 0x04, 0x0a, 0x05, 0x54, 0x6f, + 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d, + 0x61, 0x63, 0x12, 0x33, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x42, 0x1d, 0xc2, 0xdd, 0x29, 0x19, 0x0a, 0x05, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x10, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, + 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x74, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x74, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x49, 0x64, 0x12, 0x4b, 0x0a, + 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, + 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, + 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x53, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x22, 0xdc, 0x02, 0x0a, 0x11, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x49, 0x64, 0x12, 0x52, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x30, 0xc2, 0xdd, 0x29, 0x2c, 0x0a, + 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1d, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0b, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x60, 0x0a, 0x0f, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x42, 0x37, 0xc2, 0xdd, 0x29, 0x33, 0x0a, 0x0e, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x52, 0x0e, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x74, + 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x63, 0x74, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x14, 0x63, 0x65, 0x72, 0x74, + 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, + 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, + 0x65, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, + 0x49, 0x64, 0x22, 0xd4, 0x04, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, + 0x6d, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x24, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, + 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, + 0x0a, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x0a, + 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x20, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x09, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, + 0x74, 0x68, 0x52, 0x09, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x49, 0x0a, + 0x0b, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x28, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, + 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0a, 0x68, 0x74, + 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x5f, 0x0a, 0x11, 0x68, 0x74, 0x74, 0x70, + 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x0c, 0x42, 0x33, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x0f, 0x48, 0x74, 0x74, 0x70, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1c, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x52, 0x0f, 0x68, 0x74, 0x74, 0x70, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x22, 0xc3, 0x04, 0x0a, 0x0a, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x79, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, + 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x48, 0x6d, + 0x61, 0x63, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x49, 0x64, 0x12, 0x56, 0x0a, 0x11, 0x6c, 0x61, 0x73, 0x74, 0x5f, + 0x72, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, + 0x6c, 0x61, 0x73, 0x74, 0x52, 0x65, 0x6e, 0x65, 0x77, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, + 0x53, 0x0a, 0x0f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0e, 0x65, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x6e, 0x65, 0x77, + 0x61, 0x62, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x52, 0x65, + 0x6e, 0x65, 0x77, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, + 0x45, 0x5a, 0x43, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x2f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_credential_vault_store_v1_vault_proto_rawDescOnce sync.Once + file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData = file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc +) + +func file_controller_storage_credential_vault_store_v1_vault_proto_rawDescGZIP() []byte { + file_controller_storage_credential_vault_store_v1_vault_proto_rawDescOnce.Do(func() { + file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData) + }) + return file_controller_storage_credential_vault_store_v1_vault_proto_rawDescData +} + +var file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = []interface{}{ + (*CredentialStore)(nil), // 0: controller.storage.credential.vault.store.v1.CredentialStore + (*Token)(nil), // 1: controller.storage.credential.vault.store.v1.Token + (*ClientCertificate)(nil), // 2: controller.storage.credential.vault.store.v1.ClientCertificate + (*CredentialLibrary)(nil), // 3: controller.storage.credential.vault.store.v1.CredentialLibrary + (*Credential)(nil), // 4: controller.storage.credential.vault.store.v1.Credential + (*timestamp.Timestamp)(nil), // 5: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = []int32{ + 5, // 0: controller.storage.credential.vault.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 1: controller.storage.credential.vault.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 2: controller.storage.credential.vault.store.v1.CredentialStore.delete_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 3: controller.storage.credential.vault.store.v1.Token.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 4: controller.storage.credential.vault.store.v1.Token.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 5: controller.storage.credential.vault.store.v1.Token.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 6: controller.storage.credential.vault.store.v1.Token.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 7: controller.storage.credential.vault.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 8: controller.storage.credential.vault.store.v1.CredentialLibrary.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 9: controller.storage.credential.vault.store.v1.Credential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 10: controller.storage.credential.vault.store.v1.Credential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 11: controller.storage.credential.vault.store.v1.Credential.last_renewal_time:type_name -> controller.storage.timestamp.v1.Timestamp + 5, // 12: controller.storage.credential.vault.store.v1.Credential.expiration_time:type_name -> controller.storage.timestamp.v1.Timestamp + 13, // [13:13] is the sub-list for method output_type + 13, // [13:13] is the sub-list for method input_type + 13, // [13:13] is the sub-list for extension type_name + 13, // [13:13] is the sub-list for extension extendee + 0, // [0:13] is the sub-list for field type_name +} + +func init() { file_controller_storage_credential_vault_store_v1_vault_proto_init() } +func file_controller_storage_credential_vault_store_v1_vault_proto_init() { + if File_controller_storage_credential_vault_store_v1_vault_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialStore); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Token); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ClientCertificate); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialLibrary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Credential); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_credential_vault_store_v1_vault_proto_goTypes, + DependencyIndexes: file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs, + MessageInfos: file_controller_storage_credential_vault_store_v1_vault_proto_msgTypes, + }.Build() + File_controller_storage_credential_vault_store_v1_vault_proto = out.File + file_controller_storage_credential_vault_store_v1_vault_proto_rawDesc = nil + file_controller_storage_credential_vault_store_v1_vault_proto_goTypes = nil + file_controller_storage_credential_vault_store_v1_vault_proto_depIdxs = nil +} diff --git a/internal/credential/vault/supported.go b/internal/credential/vault/supported.go new file mode 100644 index 000000000..b71c29048 --- /dev/null +++ b/internal/credential/vault/supported.go @@ -0,0 +1,364 @@ +// +build linux darwin windows + +package vault + +import ( + "crypto/tls" + "database/sql" + "fmt" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/hashicorp/go-rootcerts" + vault "github.com/hashicorp/vault/api" + "github.com/ory/dockertest/v3" + "github.com/stretchr/testify/require" +) + +const DefaultVaultVersion = "1.7.2" + +func init() { + newVaultServer = gotNewServer + mountDatabase = gotMountDatabase +} + +func gotDocker(t *testing.T) {} + +func gotNewServer(t *testing.T, opt ...TestOption) *TestVaultServer { + const ( + serverTlsTemplate = `{ + "listener": [ + { + "tcp": { + "address": "0.0.0.0:8200", + "tls_disable": "false", + "tls_cert_file": "/vault/config/certificates/certificate.pem", + "tls_key_file": "/vault/config/certificates/key.pem" + } + } + ] +} +` + + clientTlsTemplate = `{ + "listener": [ + { + "tcp": { + "address": "0.0.0.0:8200", + "tls_disable": "false", + "tls_cert_file": "/vault/config/certificates/certificate.pem", + "tls_key_file": "/vault/config/certificates/key.pem", + "tls_require_and_verify_client_cert": "true", + "tls_client_ca_file": "/vault/config/certificates/client-ca-certificate.pem" + } + } + ] +} +` + ) + + require := require.New(t) + pool, err := dockertest.NewPool("") + require.NoError(err) + opts := getTestOpts(t, opt...) + + server := &TestVaultServer{ + RootToken: fmt.Sprintf("icu-root-%s", t.Name()), + pool: pool, + } + + dockerOptions := &dockertest.RunOptions{ + Repository: "vault", + Tag: DefaultVaultVersion, + Env: []string{fmt.Sprintf("VAULT_DEV_ROOT_TOKEN_ID=%s", server.RootToken)}, + } + + vConfig := vault.DefaultConfig() + + if opts.vaultTLS != TestNoTLS { + dockerOptions.Env = append(dockerOptions.Env, "VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8300") + + switch opts.vaultTLS { + case TestServerTLS: + dockerOptions.Env = append(dockerOptions.Env, fmt.Sprintf("VAULT_LOCAL_CONFIG=%s", serverTlsTemplate)) + case TestClientTLS: + dockerOptions.Env = append(dockerOptions.Env, fmt.Sprintf("VAULT_LOCAL_CONFIG=%s", clientTlsTemplate)) + } + + serverCert := testServerCert(t, testCaCert(t), "localhost") + server.serverCertBundle = serverCert + server.ServerCert = serverCert.Cert.Cert + server.CaCert = serverCert.CA.Cert + + clientTLSConfig := vConfig.HttpClient.Transport.(*http.Transport).TLSClientConfig + rootConfig := &rootcerts.Config{ + CACertificate: serverCert.CA.Cert, + } + require.NoError(rootcerts.ConfigureTLS(clientTLSConfig, rootConfig)) + + dataSrcDir := t.TempDir() + require.NoError(os.Chmod(dataSrcDir, 0o777)) + caCertFn := filepath.Join(dataSrcDir, "ca-certificate.pem") + require.NoError(ioutil.WriteFile(caCertFn, serverCert.CA.Cert, 0o777)) + certFn := filepath.Join(dataSrcDir, "certificate.pem") + require.NoError(ioutil.WriteFile(certFn, serverCert.Cert.Cert, 0o777)) + keyFn := filepath.Join(dataSrcDir, "key.pem") + require.NoError(ioutil.WriteFile(keyFn, serverCert.Cert.Key, 0o777)) + dockerOptions.Mounts = append(dockerOptions.Mounts, fmt.Sprintf("%s:/vault/config/certificates", dataSrcDir)) + + if opts.vaultTLS == TestClientTLS { + clientCert := testClientCert(t, testCaCert(t), opt...) + server.clientCertBundle = clientCert + server.ClientCert = clientCert.Cert.Cert + server.ClientKey = clientCert.Cert.Key + clientCaCertFn := filepath.Join(dataSrcDir, "client-ca-certificate.pem") + require.NoError(ioutil.WriteFile(clientCaCertFn, clientCert.CA.Cert, 0o777)) + + vaultClientCert, err := tls.X509KeyPair(server.ClientCert, server.ClientKey) + require.NoError(err) + clientTLSConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &vaultClientCert, nil + } + } + } + + // NOTE(mgaffney) 05/2021: creating a docker network is not the default + // because it added a significant amount time to the tests. + // + // For reference, running 'go test .' + // - without creating a docker network by default: 259.668s + // - with creating a docker network by default: 553.497s + // + // Machine: MacBook Pro (15-inch, 2018) + // Processor: 2.6 GHz 6-Core Intel Core i7 + // Memory: 16 GB 2400 MHz DDR4 + // OS: 10.15.7 (Catalina) + // + // Docker + // Desktop: 3.3.3 (64133) + // Engine: 20.10.6 + + if opts.dockerNetwork { + network, err := pool.CreateNetwork(t.Name()) + require.NoError(err) + server.network = network + dockerOptions.Networks = []*dockertest.Network{network} + if !opts.skipCleanup { + t.Cleanup(func() { + network.Close() + }) + } + } + + resource, err := pool.RunWithOptions(dockerOptions) + require.NoError(err) + if !opts.skipCleanup { + t.Cleanup(func() { + cleanupResource(t, pool, resource) + }) + } + server.vaultContainer = resource + + switch opts.vaultTLS { + case TestNoTLS: + server.Addr = fmt.Sprintf("http://localhost:%s", resource.GetPort("8200/tcp")) + case TestServerTLS, TestClientTLS: + server.Addr = fmt.Sprintf("https://localhost:%s", resource.GetPort("8200/tcp")) + default: + t.Fatal("unknown TLS option") + } + + vConfig.Address = server.Addr + client, err := vault.NewClient(vConfig) + require.NoError(err) + client.SetToken(server.RootToken) + + err = pool.Retry(func() error { + if _, err := client.Sys().Health(); err != nil { + return err + } + return nil + }) + require.NoError(err) + + server.addPolicy(t, "boundary-controller", requiredCapabilities) + return server +} + +func gotMountDatabase(t *testing.T, v *TestVaultServer, opt ...TestOption) *TestDatabase { + require := require.New(t) + require.Nil(v.postgresContainer, "postgres container exists") + require.NotNil(v.network, "Vault server must be created with docker network") + + pool, ok := v.pool.(*dockertest.Pool) + require.True(ok) + network, ok := v.network.(*dockertest.Network) + require.True(ok) + + dockerOptions := &dockertest.RunOptions{ + Repository: "postgres", + Tag: "11", + Networks: []*dockertest.Network{network}, + Env: []string{"POSTGRES_PASSWORD=password", "POSTGRES_DB=boundarytest"}, + } + + opts := getTestOpts(t, opt...) + + resource, err := pool.RunWithOptions(dockerOptions) + require.NoError(err) + if !opts.skipCleanup { + t.Cleanup(func() { + cleanupResource(t, pool, resource) + }) + } + v.postgresContainer = resource + + dbUrlTemplate := fmt.Sprintf("postgres://%%s:%%s@%s/boundarytest?sslmode=disable", resource.GetHostPort("5432/tcp")) + dburl := fmt.Sprintf(dbUrlTemplate, "postgres", "password") + err = pool.Retry(func() error { + var err error + db, err := sql.Open("postgres", dburl) + if err != nil { + return err + } + return db.Ping() + }) + require.NoError(err) + + const ( + createOpened = `create table boundary_opened ( name text primary key )` + createClosed = `create table boundary_closed ( name text primary key )` + + createVaultAccount = `create role vault with superuser login createrole password 'vault-password'` + createOpenedRole = `create role opened_role noinherit` + createClosedRole = `create role closed_role noinherit` + + grantOpenedRole = `grant select, insert, update, delete on boundary_opened to opened_role` + grantClosedRole = `grant select, insert, update, delete on boundary_closed to closed_role` + ) + + db, err := sql.Open("postgres", dburl) + require.NoError(err) + defer db.Close() + + exec := func(q string) { + _, err := db.Exec(q) + require.NoError(err, q) + } + exec(createOpened) + exec(createClosed) + exec(createVaultAccount) + exec(createOpenedRole) + exec(createClosedRole) + exec(grantOpenedRole) + exec(grantClosedRole) + + vc := v.client(t).cl + + // Mount Database + maxTTL := 24 * time.Hour + if deadline, ok := t.Deadline(); ok { + maxTTL = time.Until(deadline) * 2 + } + + defaultTTL := maxTTL / 2 + t.Logf("maxTTL: %s, defaultTTL: %s", maxTTL, defaultTTL) + mountInput := &vault.MountInput{ + Type: "database", + Description: t.Name(), + Config: vault.MountConfigInput{ + DefaultLeaseTTL: defaultTTL.String(), + MaxLeaseTTL: maxTTL.String(), + }, + } + mountPath := opts.mountPath + if mountPath == "" { + mountPath = "database/" + } + require.NoError(vc.Sys().Mount(mountPath, mountInput)) + policyPath := fmt.Sprintf("%s*", mountPath) + pc := pathCapabilities{ + policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability, + } + v.addPolicy(t, "database", pc) + + vaultContainer, ok := v.vaultContainer.(*dockertest.Resource) + require.True(ok) + + t.Log(vaultContainer.GetIPInNetwork(network)) + + // Configure PostgreSQL secrets engine + connUrl := fmt.Sprintf("postgresql://{{username}}:{{password}}@%s:5432/boundarytest?sslmode=disable", resource.GetIPInNetwork(network)) + t.Log(connUrl) + + postgresConfPath := path.Join(mountPath, "config/postgresql") + postgresConfOptions := map[string]interface{}{ + "plugin_name": "postgresql-database-plugin", + "connection_url": connUrl, + "allowed_roles": "opened,closed", + "username": "vault", + "password": "vault-password", + } + s, err := vc.Logical().Write(postgresConfPath, postgresConfOptions) + require.NoError(err) + require.NotEmpty(s) + + const ( + vaultOpenedCreationStatement = ` +create role "{{name}}" +with login password '{{password}}' +valid until '{{expiration}}' inherit; +grant opened_role to "{{name}}"; +` + + vaultClosedCreationStatement = ` +create role "{{name}}" +with login password '{{password}}' +valid until '{{expiration}}' inherit; +grant closed_role to "{{name}}"; +` + ) + + openedRolePath := path.Join(mountPath, "roles", "opened") + openedRoleOptions := map[string]interface{}{ + "db_name": "postgresql", + "creation_statements": vaultOpenedCreationStatement, + } + _, err = vc.Logical().Write(openedRolePath, openedRoleOptions) + require.NoError(err) + + closedRolePath := path.Join(mountPath, "roles", "closed") + closedRoleOptions := map[string]interface{}{ + "db_name": "postgresql", + "creation_statements": vaultClosedCreationStatement, + } + _, err = vc.Logical().Write(closedRolePath, closedRoleOptions) + require.NoError(err) + + return &TestDatabase{ + URL: TestDatabaseURL(dbUrlTemplate), + } +} + +func cleanupResource(t *testing.T, pool *dockertest.Pool, resource *dockertest.Resource) { + t.Helper() + var err error + for i := 0; i < 10; i++ { + err = pool.Purge(resource) + if err == nil { + return + } + time.Sleep(1 * time.Second) + } + + if strings.Contains(err.Error(), "No such container") { + return + } + t.Fatalf("Failed to cleanup local container: %s", err) +} diff --git a/internal/credential/vault/testing.go b/internal/credential/vault/testing.go new file mode 100644 index 000000000..2d85a2271 --- /dev/null +++ b/internal/credential/vault/testing.go @@ -0,0 +1,883 @@ +package vault + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "database/sql" + "encoding/pem" + "fmt" + "math/big" + mrand "math/rand" + "net" + "net/http" + "path" + "strings" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/errors" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + wrapping "github.com/hashicorp/go-kms-wrapping" + vault "github.com/hashicorp/vault/api" + "github.com/jinzhu/gorm" + _ "github.com/lib/pq" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCredentialStore creates a vault credential store in the provided DB with +// the provided scope, vault address, token, and accessor and any values passed +// in through the Options vargs. If any errors are encountered during the +// creation of the store, the test will fail. +func TestCredentialStore(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId, vaultAddr, vaultToken, accessor string, opts ...Option) *CredentialStore { + t.Helper() + ctx := context.Background() + kmsCache := kms.TestKms(t, conn, wrapper) + w := db.New(conn) + + databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + assert.NoError(t, err) + require.NotNil(t, databaseWrapper) + + cs, err := NewCredentialStore(scopeId, vaultAddr, []byte(vaultToken), opts...) + assert.NoError(t, err) + require.NotNil(t, cs) + id, err := newCredentialStoreId() + assert.NoError(t, err) + require.NotEmpty(t, id) + cs.PublicId = id + + _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, iw db.Writer) error { + require.NoError(t, iw.Create(ctx, cs)) + cert := cs.clientCert + if cert != nil { + cert.StoreId = cs.GetPublicId() + require.NoError(t, cert.encrypt(ctx, databaseWrapper)) + require.NoError(t, iw.Create(ctx, cert)) + } + return nil + }, + ) + require.NoError(t, err2) + + cs.outputToken = createTestToken(t, conn, wrapper, scopeId, id, vaultToken, accessor) + + return cs +} + +// TestCredentialStores creates count number of vault credential stores in +// the provided DB with the provided scope id. If any errors are +// encountered during the creation of the credential stores, the test will +// fail. +func TestCredentialStores(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId string, count int) []*CredentialStore { + t.Helper() + ctx := context.Background() + kmsCache := kms.TestKms(t, conn, wrapper) + w := db.New(conn) + + databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + assert.NoError(t, err) + require.NotNil(t, databaseWrapper) + + var css []*CredentialStore + for i := 0; i < count; i++ { + cs := TestCredentialStore(t, conn, wrapper, scopeId, fmt.Sprintf("http://vault%d", i), fmt.Sprintf("vault-token-%s-%d", scopeId, i), fmt.Sprintf("accessor-%s-%d", scopeId, i)) + + inCert := testClientCert(t, testCaCert(t)) + clientCert, err := NewClientCertificate(inCert.Cert.Cert, inCert.Cert.Key) + require.NoError(t, err) + require.NotEmpty(t, clientCert) + clientCert.StoreId = cs.GetPublicId() + require.NoError(t, clientCert.encrypt(ctx, databaseWrapper)) + + _, err3 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, iw db.Writer) error { + return iw.Create(ctx, clientCert) + }, + ) + require.NoError(t, err3) + + cs.clientCert = clientCert + css = append(css, cs) + } + return css +} + +// TestCredentialLibraries creates count number of vault credential +// libraries in the provided DB with the provided store id. If any errors +// are encountered during the creation of the credential libraries, the +// test will fail. +func TestCredentialLibraries(t *testing.T, conn *gorm.DB, _ wrapping.Wrapper, storeId string, count int) []*CredentialLibrary { + t.Helper() + assert, require := assert.New(t), require.New(t) + w := db.New(conn) + var libs []*CredentialLibrary + + for i := 0; i < count; i++ { + lib, err := NewCredentialLibrary(storeId, fmt.Sprintf("vault/path%d", i), WithMethod(MethodGet)) + assert.NoError(err) + require.NotNil(lib) + id, err := newCredentialLibraryId() + assert.NoError(err) + require.NotEmpty(id) + lib.PublicId = id + + ctx := context.Background() + _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, iw db.Writer) error { + return iw.Create(ctx, lib) + }, + ) + + require.NoError(err2) + libs = append(libs, lib) + } + return libs +} + +// TestCredentials creates count number of vault credentials in the provided DB with +// the provided library id and session id. If any errors are encountered +// during the creation of the credentials, the test will fail. +func TestCredentials(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, libraryId, sessionId string, count int) []*Credential { + t.Helper() + assert, require := assert.New(t), require.New(t) + rw := db.New(conn) + + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + repo, err := NewRepository(rw, rw, kms, sche) + assert.NoError(err) + require.NotNil(repo) + + lib, err := repo.LookupCredentialLibrary(ctx, libraryId) + assert.NoError(err) + require.NotNil(lib) + + store, err := repo.LookupCredentialStore(ctx, lib.GetStoreId()) + assert.NoError(err) + require.NotNil(store) + + token := store.Token() + require.NotNil(token) + + var credentials []*Credential + for i := 0; i < count; i++ { + credential, err := newCredential(lib.GetPublicId(), sessionId, fmt.Sprintf("vault/credential/%d", i), token.GetTokenHmac(), 5*time.Minute) + assert.NoError(err) + require.NotNil(credential) + + id, err := newCredentialId() + assert.NoError(err) + require.NotNil(id) + credential.PublicId = id + + query, queryValues := credential.insertQuery() + rows, err2 := rw.Exec(ctx, query, queryValues) + assert.Equal(1, rows) + assert.NoError(err2) + + credentials = append(credentials, credential) + } + return credentials +} + +func createTestToken(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId, storeId, token, accessor string) *Token { + t.Helper() + w := db.New(conn) + ctx := context.Background() + kkms := kms.TestKms(t, conn, wrapper) + databaseWrapper, err := kkms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + + inToken, err := newToken(storeId, []byte(token), []byte(accessor), 5*time.Minute) + assert.NoError(t, err) + require.NotNil(t, inToken) + + require.NoError(t, inToken.encrypt(ctx, databaseWrapper)) + query, queryValues := inToken.insertQuery() + + rows, err2 := w.Exec(ctx, query, queryValues) + assert.Equal(t, 1, rows) + require.NoError(t, err2) + + return inToken +} + +func testTokens(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, scopeId, storeId string, count int) []*Token { + t.Helper() + assert, require := assert.New(t), require.New(t) + w := db.New(conn) + + ctx := context.Background() + kkms := kms.TestKms(t, conn, wrapper) + databaseWrapper, err := kkms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + assert.NoError(err) + require.NotNil(databaseWrapper) + + r := mrand.New(mrand.NewSource(time.Now().UnixNano())) + var tokens []*Token + for i := 0; i < count; i++ { + num := r.Int31() + inToken := createTestToken(t, conn, wrapper, scopeId, storeId, fmt.Sprintf("vault-token-%s-%d-%v", storeId, i, num), fmt.Sprintf("accessor-%s-%d-%v", storeId, i, num)) + outToken := allocToken() + require.NoError(w.LookupWhere(ctx, &outToken, "token_hmac = ?", inToken.TokenHmac)) + require.NoError(outToken.decrypt(ctx, databaseWrapper)) + + tokens = append(tokens, outToken) + } + return tokens +} + +// TestVaultTLS represents the TLS configuration level of a +// TestVaultServer. +type TestVaultTLS int + +const ( + // TestNoTLS disables TLS. The test server Addr begins with http://. + TestNoTLS TestVaultTLS = iota // no TLS + + // TestServerTLS configures the Vault test server listener to use TLS. + // A CA certificate is generated and a server certificate is issued + // from the CA certificate. The CA certificate is available in the + // CaCert field of the TestVaultServer. The test server Addr begins + // with https://. + TestServerTLS + + // TestClientTLS configures the Vault test server listener to require a + // client certificate for mTLS and includes all of the settings from + // TestServerTLS. A second CA certificate is generated and a client + // certificate is issued from this CA certificate. The client + // certificate and the client certificate key are available in the in + // the ClientCert and ClientKey fields of the TestVaultServer + // respectively. + TestClientTLS +) + +type testCert struct { + Cert []byte + Key []byte + + certificate *x509.Certificate + priv *ecdsa.PrivateKey +} + +func (tc *testCert) ClientCertificate(t *testing.T) *ClientCertificate { + t.Helper() + c, err := NewClientCertificate(tc.Cert, tc.Key) + require.NoError(t, err) + return c +} + +type testCertBundle struct { + CA *testCert + Cert *testCert +} + +// testCaCert will generate a test x509 CA cert. +func testCaCert(t *testing.T) *testCert { + t.Helper() + require := require.New(t) + + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(err) + + // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature + // KeyUsage bits set in the x509.Certificate template + keyUsage := x509.KeyUsageDigitalSignature + + notBefore, notAfter := time.Now(), time.Now().Add(24*time.Hour) + if deadLine, ok := t.Deadline(); ok { + notAfter = deadLine + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + require.NoError(err) + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme CA"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage | x509.KeyUsageCertSign, + IsCA: true, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) + require.NoError(err) + + c, err := x509.ParseCertificate(derBytes) + require.NoError(err) + + k, err := x509.MarshalECPrivateKey(priv) + require.NoError(err) + + return &testCert{ + Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), + Key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: k}), + certificate: c, + priv: priv, + } +} + +// testServerCert will generate a test x509 server cert. +func testServerCert(t *testing.T, ca *testCert, hosts ...string) *testCertBundle { + t.Helper() + require := require.New(t) + + priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(err) + + // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature + // KeyUsage bits set in the x509.Certificate template + keyUsage := x509.KeyUsageDigitalSignature + + notBefore, notAfter := time.Now(), time.Now().Add(24*time.Hour) + if deadLine, ok := t.Deadline(); ok { + notAfter = deadLine + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + require.NoError(err) + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme Servers Inc"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IsCA: false, + BasicConstraintsValid: true, + } + + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca.certificate, &priv.PublicKey, ca.priv) + require.NoError(err) + + c, err := x509.ParseCertificate(derBytes) + require.NoError(err) + + k, err := x509.MarshalECPrivateKey(priv) + require.NoError(err) + + serverCert := &testCert{ + Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), + Key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: k}), + certificate: c, + priv: priv, + } + + return &testCertBundle{ + CA: ca, + Cert: serverCert, + } +} + +// testClientCert will generate a test x509 client cert. +func testClientCert(t *testing.T, ca *testCert, opt ...TestOption) *testCertBundle { + t.Helper() + require := require.New(t) + + var err error + opts := getTestOpts(t, opt...) + priv := opts.clientKey + if priv == nil { + priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(err) + } + + // ECDSA, ED25519 and RSA subject keys should have the DigitalSignature + // KeyUsage bits set in the x509.Certificate template + keyUsage := x509.KeyUsageDigitalSignature + + notBefore, notAfter := time.Now(), time.Now().Add(24*time.Hour) + if deadLine, ok := t.Deadline(); ok { + notAfter = deadLine + } + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + require.NoError(err) + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"Acme Clients Inc"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + IsCA: false, + BasicConstraintsValid: true, + } + + derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca.certificate, &priv.PublicKey, ca.priv) + require.NoError(err) + + c, err := x509.ParseCertificate(derBytes) + require.NoError(err) + + k, err := x509.MarshalECPrivateKey(priv) + require.NoError(err) + + clientCert := &testCert{ + Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}), + Key: pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: k}), + certificate: c, + priv: priv, + } + + return &testCertBundle{ + CA: ca, + Cert: clientCert, + } +} + +func getTestOpts(t *testing.T, opt ...TestOption) testOptions { + t.Helper() + opts := getDefaultTestOptions(t) + for _, o := range opt { + o(t, &opts) + } + return opts +} + +// TestOption - how Options are passed as arguments. +type TestOption func(*testing.T, *testOptions) + +// options = how options are represented +type testOptions struct { + orphan bool + periodic bool + renewable bool + policies []string + mountPath string + roleName string + vaultTLS TestVaultTLS + dockerNetwork bool + skipCleanup bool + tokenPeriod time.Duration + clientKey *ecdsa.PrivateKey +} + +func getDefaultTestOptions(t *testing.T) testOptions { + t.Helper() + defaultPeriod := 24 * time.Hour + if deadline, ok := t.Deadline(); ok { + defaultPeriod = time.Until(deadline) + } + + return testOptions{ + orphan: true, + periodic: true, + renewable: true, + policies: []string{"default", "boundary-controller"}, + mountPath: "", + roleName: "boundary", + vaultTLS: TestNoTLS, + dockerNetwork: false, + tokenPeriod: defaultPeriod, + } +} + +// WithTokenPeriod sets the period value in a vault.TokenCreateRequest when +// the token being requested is a periodic token. The default token period +// is the value of t.Deadline() or 24 hours if +// t.Deadline() is nil. +func WithTokenPeriod(d time.Duration) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.tokenPeriod = d + } +} + +// WithPolicies sets the polices to attach to a token. The default policy +// attached to tokens is 'default'. +func WithPolicies(p []string) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.policies = p + } +} + +// WithDockerNetwork sets the option to create docker network when creating +// a Vault test server. The default is to not create a docker network. +func WithDockerNetwork(b bool) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.dockerNetwork = b + } +} + +// WithTestVaultTLS sets the Vault TLS option. +// TestNoTLS is the default TLS option. +func WithTestVaultTLS(s TestVaultTLS) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.vaultTLS = s + } +} + +// WithClientKey sets the private key that will be used to generate the +// client certificate. The option is only valid when used together with TestClientTLS. +func WithClientKey(k *ecdsa.PrivateKey) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.clientKey = k + } +} + +// WithTestMountPath sets the mount path option to p. +func WithTestMountPath(p string) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + p = strings.TrimSpace(p) + if p != "" { + p = strings.TrimRight(p, "/") + "/" + } + o.mountPath = p + } +} + +// WithTestRoleName sets the roleName name to n. +// The default role name is boundary. +func WithTestRoleName(n string) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + n = strings.TrimSpace(n) + if n == "" { + n = "boundary" + } + o.roleName = n + } +} + +// WithDontCleanUp causes the resource created to not +// be automaticaly cleaned up at the end of the test run. +func WithDontCleanUp() TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.skipCleanup = true + } +} + +// TestOrphanToken sets the token orphan option to b. +// The orphan option is true by default. +func TestOrphanToken(b bool) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.orphan = b + } +} + +// TestPeriodicToken sets the token periodic option to b. +// The periodic option is true by default. +func TestPeriodicToken(b bool) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.periodic = b + } +} + +// TestRenewableToken sets the token renewable option to b. +// The renewable option is true by default. +func TestRenewableToken(b bool) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.renewable = b + } +} + +func (v *TestVaultServer) client(t *testing.T) *client { + t.Helper() + return v.clientUsingToken(t, v.RootToken) +} + +func (v *TestVaultServer) clientUsingToken(t *testing.T, token string) *client { + t.Helper() + require := require.New(t) + conf := &clientConfig{ + Addr: v.Addr, + Token: TokenSecret(token), + CaCert: v.CaCert, + ClientCert: v.ClientCert, + ClientKey: v.ClientKey, + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + require.NoError(client.ping()) + return client +} + +// CreateToken creates a new Vault token by calling /auth/token/create on v +// using v.RootToken. It returns the vault secret containing the token and +// the token itself. See +// https://www.vaultproject.io/api-docs/auth/token#create-token. +func (v *TestVaultServer) CreateToken(t *testing.T, opt ...TestOption) (*vault.Secret, string) { + t.Helper() + require := require.New(t) + opts := getTestOpts(t, opt...) + + var period string + if opts.periodic { + period = opts.tokenPeriod.String() + } + req := &vault.TokenCreateRequest{ + DisplayName: t.Name(), + NoParent: opts.orphan, + Renewable: &opts.renewable, + Period: period, + Policies: opts.policies, + } + vc := v.client(t).cl + secret, err := vc.Auth().Token().Create(req) + require.NoError(err) + require.NotNil(secret) + token, err := secret.TokenID() + require.NoError(err) + require.NotNil(token) + + return secret, token +} + +// LookupToken calls /auth/token/lookup on v for the token. See +// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token. +func (v *TestVaultServer) LookupToken(t *testing.T, token string) *vault.Secret { + t.Helper() + require := require.New(t) + vc := v.client(t).cl + secret, err := vc.Auth().Token().Lookup(token) + require.NoError(err) + require.NotNil(secret) + return secret +} + +// VerifyTokenInvalid calls /auth/token/lookup on v for the token. It expects the lookup to +// fail with a StatusForbidden. See +// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token. +func (v *TestVaultServer) VerifyTokenInvalid(t *testing.T, token string) { + t.Helper() + require, assert := require.New(t), assert.New(t) + vc := v.client(t).cl + secret, err := vc.Auth().Token().Lookup(token) + require.Error(err) + require.Nil(secret) + + // Verify error was forbidden + var respErr *vault.ResponseError + require.True(errors.As(err, &respErr)) + assert.Equal(http.StatusForbidden, respErr.StatusCode) +} + +// LookupLease calls the /sys/leases/lookup Vault endpoint and returns +// the vault.Secret response. See +// https://www.vaultproject.io/api-docs/system/leases#read-lease. +func (v *TestVaultServer) LookupLease(t *testing.T, leaseId string) *vault.Secret { + t.Helper() + require := require.New(t) + vc := v.client(t).cl + credData := map[string]interface{}{"lease_id": leaseId} + secret, err := vc.Logical().Write("sys/leases/lookup", credData) + require.NoError(err) + require.NotNil(secret) + return secret +} + +func (v *TestVaultServer) addPolicy(t *testing.T, name string, pc pathCapabilities) { + t.Helper() + require := require.New(t) + policy := pc.vaultPolicy() + require.NotEmpty(policy) + vc := v.client(t).cl + require.NoError(vc.Sys().PutPolicy(name, policy)) +} + +// MountPKI mounts the Vault PKI secret engine and initializes it by +// generating a root certificate authority and creating a default role on +// the mount. The root CA is returned. +// +// The default mount path is pki and the default role name is boundary. +// WithTestMountPath and WithTestRoleName are the only test options +// supported. +// +// MountPKI also adds a Vault policy named 'pki' to v and adds it to the +// standard set of polices attached to tokens created with v.CreateToken. +// The policy is defined as: +// +// path "mountPath/*" { +// capabilities = ["create", "read", "update", "delete", "list"] +// } +func (v *TestVaultServer) MountPKI(t *testing.T, opt ...TestOption) *vault.Secret { + t.Helper() + require := require.New(t) + opts := getTestOpts(t, opt...) + vc := v.client(t).cl + + // Mount PKI + maxTTL := 24 * time.Hour + if deadline, ok := t.Deadline(); ok { + maxTTL = time.Until(deadline) * 2 + } + + defaultTTL := maxTTL / 2 + t.Logf("maxTTL: %s, defaultTTL: %s", maxTTL, defaultTTL) + mountInput := &vault.MountInput{ + Type: "pki", + Description: t.Name(), + Config: vault.MountConfigInput{ + DefaultLeaseTTL: defaultTTL.String(), + MaxLeaseTTL: maxTTL.String(), + }, + } + mountPath := opts.mountPath + if mountPath == "" { + mountPath = "pki/" + } + require.NoError(vc.Sys().Mount(mountPath, mountInput)) + policyPath := fmt.Sprintf("%s*", mountPath) + pc := pathCapabilities{ + policyPath: createCapability | readCapability | updateCapability | deleteCapability | listCapability, + } + v.addPolicy(t, "pki", pc) + + // Generate a root CA + caPath := path.Join(mountPath, "root/generate/internal") + caOptions := map[string]interface{}{ + "common_name": t.Name(), + "ttl": maxTTL.String(), + } + s, err := vc.Logical().Write(caPath, caOptions) + require.NoError(err) + require.NotEmpty(s) + + // Create default role + rolePath := path.Join(mountPath, "roles", opts.roleName) + roleOptions := map[string]interface{}{ + "allow_any_name": true, + "ttl": defaultTTL.String(), + } + _, err = vc.Logical().Write(rolePath, roleOptions) + require.NoError(err) + + return s +} + +// TestVaultServer is a vault server running in a docker container suitable +// for testing. +type TestVaultServer struct { + RootToken string + Addr string + + CaCert []byte + ServerCert []byte + ClientCert []byte + ClientKey []byte + + serverCertBundle *testCertBundle + clientCertBundle *testCertBundle + + pool interface{} + vaultContainer interface{} + network interface{} + postgresContainer interface{} +} + +// NewTestVaultServer creates and returns a TestVaultServer. Some Vault +// secret engines require the Vault server be created with a docker +// network. Check the Mount method for the Vault secret engine to see if a +// docker network is required. +// +// WithTestVaultTLS and WithDockerNetwork are the only valid options. +// Setting the WithDockerNetwork option can significantly increase the +// amount of time required for a test to run. +func NewTestVaultServer(t *testing.T, opt ...TestOption) *TestVaultServer { + t.Helper() + return newVaultServer(t, opt...) +} + +// MountDatabase starts a PostgreSQL database in a docker container then +// mounts the Vault database secrets engine and configures it to issue +// credentials for the database. +// +// MountDatabase also adds a Vault policy named 'database' to v and adds it +// to the standard set of polices attached to tokens created with +// v.CreateToken. The policy is defined as: +// +// path "mountPath/*" { +// capabilities = ["create", "read", "update", "delete", "list"] +// } +// +// MountDatabase returns a TestDatabase for testing credentials from the +// mount. +func (v *TestVaultServer) MountDatabase(t *testing.T, opt ...TestOption) *TestDatabase { + t.Helper() + return mountDatabase(t, v, opt...) +} + +// TestDatabaseURL is a connection string with place holders for username +// and password to the database started by MountDatabase. +type TestDatabaseURL string + +// Encode encodes the username and password credentials from s into u. +func (u TestDatabaseURL) Encode(t *testing.T, s *vault.Secret) string { + t.Helper() + require := require.New(t) + require.NotNil(s, "vault.Secret") + username, ok := s.Data["username"].(string) + require.True(ok, "username") + password, ok := s.Data["password"].(string) + require.True(ok, "password") + return fmt.Sprintf(string(u), username, password) +} + +// TestDatabase is returned from MountDatabase and can be used to test +// database credentials returned by Vault for that mount. +type TestDatabase struct { + URL TestDatabaseURL +} + +// ValidateCredential tests the credentials in s against d. An error is +// returned if the credentials are not valid. +func (d *TestDatabase) ValidateCredential(t *testing.T, s *vault.Secret) error { + t.Helper() + require := require.New(t) + require.NotNil(s) + dburl := d.URL.Encode(t, s) + db, err := sql.Open("postgres", dburl) + if err != nil { + return err + } + defer db.Close() + if err := db.Ping(); err != nil { + return err + } + return nil +} diff --git a/internal/credential/vault/testing_external_test.go b/internal/credential/vault/testing_external_test.go new file mode 100644 index 000000000..23ad7eb11 --- /dev/null +++ b/internal/credential/vault/testing_external_test.go @@ -0,0 +1,59 @@ +package vault_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/session" + "github.com/hashicorp/boundary/internal/target" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestCredentials(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + cl := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 1)[0] + + hc := static.TestCatalogs(t, conn, prj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + + tar := target.TestTcpTarget(t, conn, prj.GetPublicId(), "test", target.WithHostSets([]string{hs.GetPublicId()})) + target.TestCredentialLibrary(t, conn, tar.GetPublicId(), cl.GetPublicId()) + + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + uId := at.GetIamUserId() + + sess := session.TestSession(t, conn, wrapper, session.ComposedOf{ + UserId: uId, + HostId: h.GetPublicId(), + TargetId: tar.GetPublicId(), + HostSetId: hs.GetPublicId(), + AuthTokenId: at.GetPublicId(), + ScopeId: prj.GetPublicId(), + Endpoint: "tcp://127.0.0.1:22", + }) + + count := 4 + credentials := vault.TestCredentials(t, conn, wrapper, cl.GetPublicId(), sess.GetPublicId(), count) + assert.Len(credentials, count) + for _, credential := range credentials { + assert.NotEmpty(credential.GetPublicId()) + } +} diff --git a/internal/credential/vault/testing_test.go b/internal/credential/vault/testing_test.go new file mode 100644 index 000000000..3a23f47ca --- /dev/null +++ b/internal/credential/vault/testing_test.go @@ -0,0 +1,542 @@ +package vault + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "path" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/sdk/parseutil" + vault "github.com/hashicorp/vault/api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestCredentialStores(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + count := 4 + css := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), count) + assert.Len(css, count) + for _, cs := range css { + assert.NotEmpty(cs.GetPublicId()) + assert.NotNil(cs.Token()) + assert.NotNil(cs.ClientCertificate()) + } +} + +func Test_TestCredentialLibraries(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + cs := TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + + count := 4 + libs := TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), count) + assert.Len(libs, count) + for _, lib := range libs { + assert.NotEmpty(lib.GetPublicId()) + } +} + +func testLogVaultSecret(t *testing.T, v *vault.Secret) string { + t.Helper() + require := require.New(t) + require.NotNil(v) + b, err := json.MarshalIndent(v, "", " ") + require.NoError(err) + require.NotEmpty(b) + return string(b) +} + +func TestTestVaultServer_CreateToken(t *testing.T) { + t.Parallel() + assertIsRenewable := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsRenewable" + return func(t *testing.T, s *vault.Secret) { + isRenewable, err := s.TokenIsRenewable() + assert.NoError(t, err, op) + assert.True(t, isRenewable, op) + assert.Equal(t, isRenewable, s.Auth.Renewable, op) + } + } + + assertIsNotRenewable := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsNotRenewable" + return func(t *testing.T, s *vault.Secret) { + isRenewable, err := s.TokenIsRenewable() + assert.NoError(t, err, op) + assert.False(t, isRenewable, op) + assert.Equal(t, isRenewable, s.Auth.Renewable, op) + } + } + + assertIsOrphan := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsOrphan" + return func(t *testing.T, s *vault.Secret) { + assert.True(t, s.Auth.Orphan, op) + } + } + + assertIsNotOrphan := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsNotOrphan" + return func(t *testing.T, s *vault.Secret) { + assert.False(t, s.Auth.Orphan, op) + } + } + + assertIsPeriodic := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsPeriodic" + return func(t *testing.T, s *vault.Secret) { + _, ok := s.Data["period"] + assert.True(t, ok, op) + } + } + + assertIsNotPeriodic := func() func(t *testing.T, s *vault.Secret) { + const op = "assertIsNotPeriodic" + return func(t *testing.T, s *vault.Secret) { + _, ok := s.Data["period"] + assert.False(t, ok, op) + } + } + + assertPeriod := func(want time.Duration) func(t *testing.T, s *vault.Secret) { + const op = "assertPeriod" + return func(t *testing.T, s *vault.Secret) { + period, ok := s.Data["period"] + if assert.True(t, ok, op) { + require.NotNil(t, period) + gotPeriod, err := parseutil.ParseDurationSecond(period) + require.NoError(t, err) + if assert.True(t, ok, op) { + delta := 1 * time.Minute + assert.InDelta(t, want.Seconds(), gotPeriod.Seconds(), delta.Seconds()) + } + } + } + } + + assertDefaultPeriod := func() func(t *testing.T, s *vault.Secret) { + defaultPeriod := 24 * time.Hour + if deadline, ok := t.Deadline(); ok { + defaultPeriod = time.Until(deadline) + } + return assertPeriod(defaultPeriod) + } + + combine := func(fns ...func(*testing.T, *vault.Secret)) func(*testing.T, *vault.Secret) { + return func(t *testing.T, s *vault.Secret) { + for _, fn := range fns { + fn(t, s) + } + } + } + + tests := []struct { + name string + opts []TestOption + tokenChkFn func(t *testing.T, token *vault.Secret) + lookupChkFn func(t *testing.T, lookup *vault.Secret) + }{ + { + name: "DefaultOptions", + tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()), + lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()), + }, + { + name: "NotPeriodic", + opts: []TestOption{TestPeriodicToken(false)}, + tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()), + lookupChkFn: assertIsNotPeriodic(), + }, + { + name: "NotOrphan", + opts: []TestOption{TestOrphanToken(false)}, + tokenChkFn: combine(assertIsRenewable(), assertIsNotOrphan()), + lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()), + }, + { + name: "NotRenewable", + opts: []TestOption{TestRenewableToken(false)}, + tokenChkFn: combine(assertIsNotRenewable(), assertIsOrphan()), + lookupChkFn: combine(assertIsPeriodic(), assertDefaultPeriod()), + }, + { + name: "TokenPeriod", + opts: []TestOption{WithTokenPeriod(3 * time.Hour)}, + tokenChkFn: combine(assertIsRenewable(), assertIsOrphan()), + lookupChkFn: combine(assertIsPeriodic(), assertPeriod(3*time.Hour)), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t) + require.NotNil(v) + secret, token := v.CreateToken(t, tt.opts...) + require.NotNil(secret) + require.NotEmpty(token) + require.Equal(token, secret.Auth.ClientToken) + t.Log(testLogVaultSecret(t, secret)) + + // token sanity check + t2, err := secret.TokenID() + require.NoError(err) + assert.NotEmpty(t2) + require.Equal(token, t2) + require.Equal(t2, secret.Auth.ClientToken) + + if tt.tokenChkFn != nil { + tt.tokenChkFn(t, secret) + } + + tokenLookup := v.LookupToken(t, token) + t.Log(testLogVaultSecret(t, tokenLookup)) + + if tt.lookupChkFn != nil { + tt.lookupChkFn(t, tokenLookup) + } + }) + } +} + +func TestNewVaultServer(t *testing.T) { + t.Parallel() + t.Run("TestNoTLS", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS)) + require.NotNil(v) + + assert.NotEmpty(v.RootToken) + assert.NotEmpty(v.Addr) + + conf := &clientConfig{ + Addr: v.Addr, + Token: TokenSecret(v.RootToken), + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + require.NoError(client.ping()) + }) + t.Run("TestServerTLS", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS)) + require.NotNil(v) + + assert.NotEmpty(v.RootToken) + assert.NotEmpty(v.Addr) + assert.NotEmpty(v.CaCert) + + conf := &clientConfig{ + Addr: v.Addr, + Token: TokenSecret(v.RootToken), + CaCert: v.CaCert, + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + require.NoError(client.ping()) + }) + t.Run("TestClientTLS", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS)) + require.NotNil(v) + + assert.NotEmpty(v.RootToken) + assert.NotEmpty(v.Addr) + assert.NotEmpty(v.CaCert) + assert.NotEmpty(v.ClientCert) + assert.NotEmpty(v.ClientKey) + + conf := &clientConfig{ + Addr: v.Addr, + Token: TokenSecret(v.RootToken), + CaCert: v.CaCert, + ClientCert: v.ClientCert, + ClientKey: v.ClientKey, + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + require.NoError(client.ping()) + }) + t.Run("TestClientTLS-with-client-key", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(err) + + v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS), WithClientKey(key)) + require.NotNil(v) + + assert.NotEmpty(v.RootToken) + assert.NotEmpty(v.Addr) + assert.NotEmpty(v.CaCert) + assert.NotEmpty(v.ClientCert) + assert.NotEmpty(v.ClientKey) + + k, err := x509.MarshalECPrivateKey(key) + require.NoError(err) + assert.Equal(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: k}), v.ClientKey) + + conf := &clientConfig{ + Addr: v.Addr, + Token: TokenSecret(v.RootToken), + CaCert: v.CaCert, + ClientCert: v.ClientCert, + ClientKey: v.ClientKey, + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + require.NoError(client.ping()) + }) +} + +func TestTestVaultServer_MountPKI(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestNoTLS)) + require.NotNil(v) + + vc := v.client(t).cl + mounts, err := vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + beforeCount := len(mounts) + + v.MountPKI(t) + + mounts, err = vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + afterCount := len(mounts) + assert.Greater(afterCount, beforeCount) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"})) + vc.SetToken(token) + + certPath := path.Join("pki", "issue", "boundary") + certOptions := map[string]interface{}{ + "common_name": "boundary.com", + } + certSecret, err := vc.Logical().Write(certPath, certOptions) + assert.NoError(err) + require.NotEmpty(certSecret) + }) + t.Run("with-mount-path", func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS)) + require.NotNil(v) + + vc := v.client(t).cl + mounts, err := vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + beforeCount := len(mounts) + + v.MountPKI(t, WithTestMountPath("gary")) + + mounts, err = vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + afterCount := len(mounts) + assert.Greater(afterCount, beforeCount) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"})) + vc.SetToken(token) + + certPath := path.Join("gary", "issue", "boundary") + certOptions := map[string]interface{}{ + "common_name": "boundary.com", + } + certSecret, err := vc.Logical().Write(certPath, certOptions) + assert.NoError(err) + require.NotEmpty(certSecret) + }) + t.Run("with-role-name", func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithTestVaultTLS(TestClientTLS)) + require.NotNil(v) + + vc := v.client(t).cl + mounts, err := vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + beforeCount := len(mounts) + + v.MountPKI(t, WithTestRoleName("gary")) + + mounts, err = vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + afterCount := len(mounts) + assert.Greater(afterCount, beforeCount) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "pki"})) + vc.SetToken(token) + + certPath := path.Join("pki", "issue", "gary") + certOptions := map[string]interface{}{ + "common_name": "boundary.com", + } + certSecret, err := vc.Logical().Write(certPath, certOptions) + assert.NoError(err) + require.NotEmpty(certSecret) + }) +} + +func TestTestVaultServer_MountDatabase(t *testing.T) { + t.Run("defaults", func(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true), WithTestVaultTLS(TestClientTLS)) + vc := v.client(t).cl + + mounts, err := vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + beforeCount := len(mounts) + + testDatabase := v.MountDatabase(t) + + mounts, err = vc.Sys().ListMounts() + assert.NoError(err) + require.NotEmpty(mounts) + afterCount := len(mounts) + + assert.Greater(afterCount, beforeCount) + + _, token := v.CreateToken(t, WithPolicies([]string{"default", "boundary-controller", "database"})) + vc.SetToken(token) + + dbSecret, err := vc.Logical().Read(path.Join("database", "creds", "opened")) + assert.NoError(err) + require.NotEmpty(dbSecret) + + // verify the database credentials work + assert.NoError(testDatabase.ValidateCredential(t, dbSecret)) + + // revoke the database credentials + assert.NoError(vc.Sys().Revoke(dbSecret.LeaseID)) + + // verify the database credentials no longer work + assert.Error(testDatabase.ValidateCredential(t, dbSecret)) + }) +} + +func TestTestVaultServer_LookupLease(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + conf := &clientConfig{ + Addr: v.Addr, + CaCert: v.CaCert, + ClientCert: v.ClientCert, + ClientKey: v.ClientKey, + Token: TokenSecret(v.RootToken), + } + + client, err := newClient(conf) + require.NoError(err) + require.NotNil(client) + assert.NoError(client.ping()) + + // Create secret + credPath := path.Join("database", "creds", "opened") + cred, err := client.get(credPath) + require.NoError(err) + + // Sleep to move ttl + time.Sleep(time.Second) + + leaseLookup := v.LookupLease(t, cred.LeaseID) + require.NotNil(leaseLookup.Data) + + id := leaseLookup.Data["id"] + require.NotEmpty(id) + assert.Equal(cred.LeaseID, id.(string)) + + ttl := leaseLookup.Data["ttl"] + require.NotEmpty(ttl) + newTtl, err := ttl.(json.Number).Int64() + require.NoError(err) + // New ttl should have moved and be lower than original lease duration + assert.True(cred.LeaseDuration > int(newTtl)) +} + +func TestTestVaultServer_VerifyTokenInvalid(t *testing.T) { + t.Parallel() + require := require.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + + _, token := v.CreateToken(t) + client := v.clientUsingToken(t, token) + err := client.revokeToken() + require.NoError(err) + v.VerifyTokenInvalid(t, token) + + // Verify fake token is not valid + v.VerifyTokenInvalid(t, "fake-token") +} + +func Test_testClientCert(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cert := testClientCert(t, testCaCert(t)) + require.NotNil(cert) + assert.NotEmpty(cert.Cert.Cert) + assert.NotEmpty(cert.Cert.Key) + + key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(err) + + cert1 := testClientCert(t, testCaCert(t), WithClientKey(key)) + require.NotNil(cert1) + assert.NotEmpty(cert1.Cert.Cert) + assert.NotEmpty(cert1.Cert.Key) + + // cert and cert1 should have different certs and keys + assert.NotEqual(cert1.Cert.Cert, cert.Cert.Cert) + assert.NotEqual(cert1.Cert.Key, cert.Cert.Key) + + // Generate new cert with same key as cert1 + cert2 := testClientCert(t, testCaCert(t), WithClientKey(key)) + require.NotNil(cert2) + assert.NotEmpty(cert2.Cert.Cert) + assert.NotEmpty(cert2.Cert.Key) + + // cert1 and cert2 should have different certs but the same key + assert.NotEqual(cert1.Cert.Cert, cert2.Cert.Cert) + assert.Equal(cert1.Cert.Key, cert2.Cert.Key) +} diff --git a/internal/credential/vault/vault.go b/internal/credential/vault/vault.go new file mode 100644 index 000000000..dbdc292e7 --- /dev/null +++ b/internal/credential/vault/vault.go @@ -0,0 +1,242 @@ +package vault + +import ( + "context" + "crypto/tls" + "fmt" + "net/http" + "strings" + "time" + + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/go-rootcerts" + vault "github.com/hashicorp/vault/api" + "github.com/mitchellh/mapstructure" +) + +type clientConfig struct { + Addr string + Token TokenSecret + CaCert []byte + ClientCert []byte + ClientKey KeySecret + TlsServerName string + TlsSkipVerify bool + Namespace string +} + +func (c *clientConfig) isValid() bool { + if c == nil || c.Addr == "" || len(c.Token) < 0 { + return false + } + return true +} + +func (c *clientConfig) isClientTLS() bool { + if len(c.ClientCert) > 0 && len(c.ClientKey) > 0 { + return true + } + return false +} + +type client struct { + cl *vault.Client + token TokenSecret +} + +func newClient(c *clientConfig) (*client, error) { + const op = "vault.newClient" + if !c.isValid() { + return nil, errors.New(errors.InvalidParameter, op, "invalid configuration") + } + vc := vault.DefaultConfig() + vc.Address = c.Addr + if len(c.CaCert) > 0 { + rootConfig := &rootcerts.Config{ + CACertificate: c.CaCert, + } + tlsConfig := vc.HttpClient.Transport.(*http.Transport).TLSClientConfig + tlsConfig.InsecureSkipVerify = c.TlsSkipVerify + if err := rootcerts.ConfigureTLS(tlsConfig, rootConfig); err != nil { + return nil, errors.Wrap(err, op) + } + } + + if c.isClientTLS() { + clientCert, err := tls.X509KeyPair(c.ClientCert, c.ClientKey) + if err != nil { + return nil, errors.Wrap(err, op) + } + tlsConfig := vc.HttpClient.Transport.(*http.Transport).TLSClientConfig + tlsConfig.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + return &clientCert, nil + } + } + + vClient, err := vault.NewClient(vc) + if err != nil { + return nil, errors.Wrap(err, op) + } + vClient.SetToken(string(c.Token)) + + return &client{ + cl: vClient, + token: c.Token, + }, nil +} + +// ping calls the /sys/health Vault endpoint and returns an error if no +// response is returned. This endpoint is accessible with the default +// policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/system/health#read-health-information. +func (c *client) ping() error { + const op = "vault.(client).ping" + h, err := c.cl.Sys().Health() + switch { + case err != nil: + return errors.Wrap(err, op, errors.WithCode(errors.Unknown), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + case h == nil: + return errors.New(errors.Unavailable, op, fmt.Sprintf("no repsonse: vault: %s", c.cl.Address())) + case !h.Initialized || h.Sealed: + return errors.New(errors.Unavailable, op, fmt.Sprintf("vault (%s): initialized: %t, sealed: %t ", c.cl.Address(), h.Initialized, h.Sealed)) + } + + return nil +} + +// renewToken calls the /auth/token/renew-self Vault endpoint and returns +// the vault.Secret response. This endpoint is accessible with the default +// policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self. +func (c *client) renewToken() (*vault.Secret, error) { + const op = "vault.(client).renewToken" + t, err := c.cl.Auth().Token().RenewSelf(0) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithCode(errors.Unknown), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return t, nil +} + +// revokeToken calls the /auth/token/revoke-self Vault endpoint. This +// endpoint is accessible with the default policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/auth/token#revoke-a-token-self. +func (c *client) revokeToken() error { + const op = "vault.(client).revokeToken" + // The `token` parameter is kept for backwards compatibility but is ignored, so use "" + if err := c.cl.Auth().Token().RevokeSelf(""); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Unknown), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return nil +} + +// renewLease calls the /sys/leases/renew Vault endpoint and returns the +// vault.Secret response. This endpoint is accessible with the default +// policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/system/leases#renew-lease. +func (c *client) renewLease(leaseId string, leaseDuration time.Duration) (*vault.Secret, error) { + const op = "vault.(client).renewLease" + t, err := c.cl.Sys().Renew(leaseId, int(leaseDuration.Round(time.Second).Seconds())) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithCode(errors.VaultCredentialRequest), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return t, nil +} + +// revokeLease calls the /sys/leases/revoke Vault endpoint. This endpoint +// is NOT accessible with the default policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/system/leases#revoke-lease. +func (c *client) revokeLease(leaseId string) error { + const op = "vault.(client).revokeLease" + if err := c.cl.Sys().Revoke(leaseId); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Unknown), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return nil +} + +// lookupToken calls the /auth/token/lookup-self Vault endpoint and returns +// the vault.Secret response. This endpoint is accessible with the default +// policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/auth/token#lookup-a-token-self. +func (c *client) lookupToken() (*vault.Secret, error) { + const op = "vault.(client).lookupToken" + t, err := c.cl.Auth().Token().LookupSelf() + if err != nil { + return nil, errors.Wrap(err, op, errors.WithCode(errors.Unknown), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return t, nil +} + +// swapToken replaces the token in the Vault client with t and returns the +// token that was replaced. +func (c *client) swapToken(new TokenSecret) (old TokenSecret) { + old = TokenSecret(c.cl.Token()) + c.cl.SetToken(string(new)) + return +} + +func (c *client) get(path string) (*vault.Secret, error) { + const op = "vault.(client).get" + s, err := c.cl.Logical().Read(path) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithCode(errors.VaultCredentialRequest), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return s, nil +} + +func (c *client) post(path string, data []byte) (*vault.Secret, error) { + const op = "vault.(client).post" + + if len(data) == 0 { + // For POST and PUT methods, Vault requires a valid JSON object be + // sent even if the JSON object is empty + data = []byte(`{}`) + } + s, err := c.cl.Logical().WriteBytes(path, data) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithCode(errors.VaultCredentialRequest), errors.WithMsg(fmt.Sprintf("vault: %s", c.cl.Address()))) + } + return s, nil +} + +// capabilities calls the /sys/capabilities-self Vault endpoint and returns +// the vault.Secret response. This endpoint is accessible with the default +// policy in Vault 1.7.2. See +// https://www.vaultproject.io/api-docs/system/capabilities-self. +func (c *client) capabilities(paths []string) (pathCapabilities, error) { + const op = "vault.(client).capabilities" + if len(paths) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "empty paths") + } + body := map[string]string{ + "paths": strings.Join(paths, ","), + } + reqPath := "/v1/sys/capabilities-self" + + r := c.cl.NewRequest("POST", reqPath) + if err := r.SetJSONBody(body); err != nil { + return nil, err + } + + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + resp, err := c.cl.RawRequestWithContext(ctx, r) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + secret, err := vault.ParseSecret(resp.Body) + if err != nil { + return nil, err + } + if secret == nil || secret.Data == nil { + return nil, errors.New(errors.Unknown, op, "data from Vault is empty") + } + + var res map[string][]string + if err := mapstructure.Decode(secret.Data, &res); err != nil { + return nil, err + } + + return newPathCapabilities(res), nil +} diff --git a/internal/credential/vault/vault_capabilities.go b/internal/credential/vault/vault_capabilities.go new file mode 100644 index 000000000..bcd8228cb --- /dev/null +++ b/internal/credential/vault/vault_capabilities.go @@ -0,0 +1,270 @@ +package vault + +import ( + "fmt" + "sort" + "strings" +) + +type capabilities uint + +const noCapabilities = capabilities(0) + +const ( + denyCapability capabilities = 1 << iota + createCapability + readCapability + updateCapability + deleteCapability + listCapability + sudoCapability + rootCapability +) + +var string2Cap = map[string]capabilities{ + "deny": denyCapability, + "create": createCapability, + "read": readCapability, + "update": updateCapability, + "delete": deleteCapability, + "list": listCapability, + "sudo": sudoCapability, + "root": rootCapability, +} + +func (c capabilities) goStrings() []string { + if c == noCapabilities { + return nil + } + + var caps []string + if c&denyCapability == denyCapability { + caps = append(caps, "denyCapability") + } + if c&createCapability == createCapability { + caps = append(caps, "createCapability") + } + if c&readCapability == readCapability { + caps = append(caps, "readCapability") + } + if c&updateCapability == updateCapability { + caps = append(caps, "updateCapability") + } + if c&deleteCapability == deleteCapability { + caps = append(caps, "deleteCapability") + } + if c&listCapability == listCapability { + caps = append(caps, "listCapability") + } + if c&sudoCapability == sudoCapability { + caps = append(caps, "sudoCapability") + } + if c&rootCapability == rootCapability { + caps = append(caps, "rootCapability") + } + return caps +} + +func (c capabilities) GoString() string { + if c == noCapabilities { + return "vault.capabilities{}" + } + caps := c.goStrings() + var b strings.Builder + b.WriteString("vault.capabilities{ ") + b.WriteString(strings.Join(caps, " | ")) + b.WriteString(" }") + return b.String() +} + +func (c capabilities) strings() []string { + if c == noCapabilities { + return nil + } + + var caps []string + if c&denyCapability == denyCapability { + caps = append(caps, "deny") + } + if c&createCapability == createCapability { + caps = append(caps, "create") + } + if c&readCapability == readCapability { + caps = append(caps, "read") + } + if c&updateCapability == updateCapability { + caps = append(caps, "update") + } + if c&deleteCapability == deleteCapability { + caps = append(caps, "delete") + } + if c&listCapability == listCapability { + caps = append(caps, "list") + } + if c&sudoCapability == sudoCapability { + caps = append(caps, "sudo") + } + if c&rootCapability == rootCapability { + caps = append(caps, "root") + } + + return caps +} + +func (c capabilities) String() string { + if c == noCapabilities { + return "[]" + } + caps := c.strings() + var b strings.Builder + b.WriteString(`["`) + b.WriteString(strings.Join(caps, `", "`)) + b.WriteString(`"]`) + return b.String() +} + +func (c capabilities) hasDeny() bool { + return c&denyCapability == denyCapability +} + +func (c capabilities) missing(required capabilities) capabilities { + return required &^ c +} + +type pathCapabilities map[string]capabilities + +func newPathCapabilities(results map[string][]string) pathCapabilities { + pc := make(pathCapabilities, len(results)) + for path, caps := range results { + for _, cap := range caps { + pc[path] |= string2Cap[cap] + } + } + return pc +} + +// has reports if the path has all of the required capabilities. +// has always returns false if deny has been set on the path. +// deny is ignored if it is included in the list of required capabilities. +func (pc pathCapabilities) has(path string, required ...capabilities) bool { + if pc[path].hasDeny() { + return false + } + var allRequiredCaps capabilities + for _, cap := range required { + allRequiredCaps |= cap + } + // deny cannot be in the required list + allRequiredCaps &^= denyCapability + return pc[path]&allRequiredCaps != 0 +} + +// get returns the capabilities for path and a boolean to indicate if the +// path was set. +func (pc pathCapabilities) get(path string) (capabilities, bool) { + c, ok := pc[path] + return c, ok +} + +// union creates and returns a new pathCapabilities instance, z. union sets +// z[p] = x[p] | y[p] for all p, where p is a path from the set of paths +// from x and y. +func (x pathCapabilities) union(y pathCapabilities) (z pathCapabilities) { + if len(x) == 0 && len(y) == 0 { + return + } + max := func(a, b int) int { + if a < b { + return a + } + return b + } + z = make(pathCapabilities, max(len(x), len(y))) + for p := range x { + z[p] = x[p] | y[p] + } + for p := range y { + z[p] = x[p] | y[p] + } + return +} + +func (pc pathCapabilities) missing(required pathCapabilities) pathCapabilities { + switch { + case len(required) == 0: + return nil + case len(pc) == 0: + z := make(pathCapabilities, len(required)) + for p := range required { + z[p] = required[p] + } + return z + } + + var z pathCapabilities + for p, c := range required { + mc := pc[p].missing(c) + if mc != noCapabilities { + if z == nil { + z = make(pathCapabilities) + } + z[p] = mc + } + } + return z +} + +func (pc pathCapabilities) vaultPolicy() string { + if len(pc) == 0 { + return "" + } + + var paths []string + for path := range pc { + if pc[path] != noCapabilities { + paths = append(paths, path) + } + } + if len(paths) == 0 { + return "" + } + + sort.Strings(paths) + var sep string + b := new(strings.Builder) + for _, path := range paths { + b.WriteString(sep) + fmt.Fprintf(b, "path %q {\n", path) + fmt.Fprintf(b, "\tcapabilities = %s\n", pc[path]) + b.WriteString("}\n") + sep = "\n" + } + return b.String() +} + +func (pc pathCapabilities) paths() []string { + var paths []string + for path := range pc { + paths = append(paths, path) + } + return paths +} + +func (pc pathCapabilities) String() string { + var b strings.Builder + for k, v := range pc { + b.WriteString(k) + b.WriteString(": ") + b.WriteString(strings.Join(v.strings(), "|")) + b.WriteString(", ") + } + return strings.TrimSuffix(b.String(), ", ") +} + +var requiredCapabilities = pathCapabilities{ + "auth/token/lookup-self": readCapability, + "auth/token/renew-self": updateCapability, + "auth/token/revoke-self": updateCapability, + "sys/leases/renew": updateCapability, + "sys/leases/revoke": updateCapability, +} diff --git a/internal/credential/vault/vault_capabilities_test.go b/internal/credential/vault/vault_capabilities_test.go new file mode 100644 index 000000000..fe3e43b75 --- /dev/null +++ b/internal/credential/vault/vault_capabilities_test.go @@ -0,0 +1,633 @@ +package vault + +import ( + "fmt" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPathCapabilities_has(t *testing.T) { + type hasCheck struct { + p string + t []capabilities + f []capabilities + } + + tests := []struct { + name string + given map[string][]string + checks []hasCheck + }{ + { + name: "empty", + checks: []hasCheck{ + { + p: "one", + f: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-no-capabilities", + given: map[string][]string{ + "one": {""}, + }, + checks: []hasCheck{ + { + p: "one", + f: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-deny", + given: map[string][]string{ + "one": {"deny"}, + }, + checks: []hasCheck{ + { + p: "one", + f: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-deny-and-others", + given: map[string][]string{ + "one": {"deny", "create", "update"}, + }, + checks: []hasCheck{ + { + p: "one", + f: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-one-capability", + given: map[string][]string{ + "one": {"create"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + createCapability, + }, + f: []capabilities{ + readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-multiple-capabilities", + given: map[string][]string{ + "one": {"create", "update"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + createCapability, updateCapability, + }, + f: []capabilities{ + readCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-all-capabilities", + given: map[string][]string{ + "one": {"create", "read", "update", "delete", "list", "sudo", "root"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-all-capabilities-plus-unknown", + given: map[string][]string{ + "one": {"create", "read", "update", "delete", "list", "sudo", "root", "stuff"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "one-path-all-capabilities-ignore-required-deny", + given: map[string][]string{ + "one": {"create", "read", "update", "delete", "list", "sudo", "root"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + denyCapability, + createCapability, readCapability, updateCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + { + name: "two-paths-multiple-capabilities", + given: map[string][]string{ + "one": {"create", "update"}, + "two": {"read", "update"}, + }, + checks: []hasCheck{ + { + p: "one", + t: []capabilities{ + createCapability, updateCapability, + }, + f: []capabilities{ + readCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + { + p: "two", + t: []capabilities{ + readCapability, updateCapability, + }, + f: []capabilities{ + createCapability, + deleteCapability, listCapability, sudoCapability, + rootCapability, + }, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got := newPathCapabilities(tt.given) + for _, check := range tt.checks { + for _, cap := range check.t { + switch { + case cap.hasDeny(): + assert.False(got.has(check.p, cap)) + default: + assert.True(got.has(check.p, cap)) + } + } + if len(check.t) > 0 { + assert.True(got.has(check.p, check.t...)) + } + for _, cap := range check.f { + assert.False(got.has(check.p, cap)) + } + } + }) + } +} + +func TestPathCapabilities_get(t *testing.T) { + type getCheck struct { + p string + want capabilities + wantOk bool + } + + tests := []struct { + name string + given map[string][]string + checks []getCheck + }{ + { + name: "empty", + checks: []getCheck{ + { + p: "one", + }, + }, + }, + { + name: "one-path-no-capabilities", + given: map[string][]string{ + "one": {""}, + }, + checks: []getCheck{ + { + p: "one", + wantOk: true, + }, + }, + }, + { + name: "one-path-deny", + given: map[string][]string{ + "one": {"deny"}, + }, + checks: []getCheck{ + { + p: "one", + wantOk: true, + want: denyCapability, + }, + }, + }, + { + name: "one-path-deny-and-others", + given: map[string][]string{ + "one": {"deny", "create", "update"}, + }, + checks: []getCheck{ + { + p: "one", + wantOk: true, + want: denyCapability | createCapability | updateCapability, + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + pc := newPathCapabilities(tt.given) + for _, check := range tt.checks { + got, gotOk := pc.get(check.p) + assert.Equalf(check.want, got, "capabilities: want: %q got: %q", check.want, got) + assert.Equal(check.wantOk, gotOk, "ok") + } + }) + } +} + +func TestPathCapabilities_vaultPolicy(t *testing.T) { + const ( + onePathOneCap string = `path "one" { + capabilities = ["create"] +} +` + + twoPathsOneCap string = `path "one" { + capabilities = ["create"] +} + +path "two" { + capabilities = ["create"] +} +` + + onePathTwoCaps string = `path "one" { + capabilities = ["create", "list"] +} +` + ) + + tests := []struct { + name string + given pathCapabilities + want string + }{ + { + name: "empty", + }, + { + name: "onePathNoCap", + given: pathCapabilities{ + "one": noCapabilities, + }, + }, + { + name: "onePathOneCap", + want: onePathOneCap, + given: pathCapabilities{ + "one": createCapability, + }, + }, + { + name: "twoPathsOneAndNoCap", + want: onePathOneCap, + given: pathCapabilities{ + "one": createCapability, + "two": noCapabilities, + }, + }, + { + name: "twoPathsOneCap", + want: twoPathsOneCap, + given: pathCapabilities{ + "one": createCapability, + "two": createCapability, + }, + }, + { + name: "onePathTwoCaps", + want: onePathTwoCaps, + given: pathCapabilities{ + "one": createCapability | listCapability, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := tt.given.vaultPolicy() + t.Log(got) + assert.Equal(t, tt.want, got) + }) + } +} + +func assertDifferentMaps(t *testing.T, a, b, got pathCapabilities) { + t.Helper() + assert := assert.New(t) + aptr, bptr, gotptr := reflect.ValueOf(a).Pointer(), reflect.ValueOf(b).Pointer(), reflect.ValueOf(got).Pointer() + if a != nil { + assert.NotEqual(aptr, gotptr, "a and got are the same map") + } + if b != nil { + assert.NotEqual(bptr, gotptr, "b and got are the same map") + } + if a != nil && b != nil { + assert.NotEqual(aptr, bptr, "a and b are the same map") + } +} + +func TestPathCapabilities_union(t *testing.T) { + check := func(t *testing.T, x, y, z pathCapabilities) { + got := x.union(y) + assert.Equal(t, z, got) + assertDifferentMaps(t, x, y, got) + } + tests := []struct { + name string + x, y, z pathCapabilities + }{ + { + name: "x-y-empty", + }, + { + name: "x-empty", + y: pathCapabilities{"one": createCapability}, + z: pathCapabilities{"one": createCapability}, + }, + { + name: "y-empty", + x: pathCapabilities{"one": createCapability}, + z: pathCapabilities{"one": createCapability}, + }, + { + name: "identical", + x: pathCapabilities{"one": createCapability}, + y: pathCapabilities{"one": createCapability}, + z: pathCapabilities{"one": createCapability}, + }, + { + name: "multiple-capabilities", + x: pathCapabilities{"one": createCapability}, + y: pathCapabilities{"one": listCapability}, + z: pathCapabilities{"one": createCapability | listCapability}, + }, + { + name: "multiple-paths", + x: pathCapabilities{"one": createCapability}, + y: pathCapabilities{"two": createCapability}, + z: pathCapabilities{"one": createCapability, "two": createCapability}, + }, + { + name: "multiple-paths-multiple-capabilities", + x: pathCapabilities{ + "one": createCapability | listCapability, + "two": createCapability | readCapability, + "three": sudoCapability, + }, + y: pathCapabilities{ + "one": createCapability | updateCapability, + "two": createCapability | deleteCapability, + "four": denyCapability, + }, + z: pathCapabilities{ + "one": createCapability | listCapability | updateCapability, + "two": createCapability | readCapability | deleteCapability, + "three": sudoCapability, + "four": denyCapability, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + // test the union method is commutative + check(t, tt.x, tt.y, tt.z) + check(t, tt.y, tt.x, tt.z) + }) + } +} + +func TestPathCapabilities_missing(t *testing.T) { + tests := []struct { + name string + have, require, want pathCapabilities + }{ + { + name: "empty-empty", + }, + { + name: "empty-have", + require: pathCapabilities{"one": createCapability}, + want: pathCapabilities{"one": createCapability}, + }, + { + name: "empty-required", + have: pathCapabilities{"one": createCapability}, + }, + { + name: "have-and-require-equal", + have: pathCapabilities{"one": createCapability}, + require: pathCapabilities{"one": createCapability}, + }, + { + name: "one-path-missing-capability", + have: pathCapabilities{"one": createCapability}, + require: pathCapabilities{"one": readCapability}, + want: pathCapabilities{"one": readCapability}, + }, + { + name: "one-path-missing-multiple-capabilities", + have: pathCapabilities{"one": createCapability}, + require: pathCapabilities{"one": createCapability | readCapability | updateCapability}, + want: pathCapabilities{"one": readCapability | updateCapability}, + }, + { + name: "two-paths-one-missing-capabilities-one-not", + have: pathCapabilities{"one": createCapability, "two": createCapability}, + require: pathCapabilities{"one": readCapability, "two": createCapability}, + want: pathCapabilities{"one": readCapability}, + }, + { + name: "two-paths-both-missing-capabilities", + have: pathCapabilities{"one": createCapability, "two": createCapability}, + require: pathCapabilities{"one": readCapability, "two": readCapability}, + want: pathCapabilities{"one": readCapability, "two": readCapability}, + }, + { + name: "have-one-path-require-two-paths", + have: pathCapabilities{"one": createCapability}, + require: pathCapabilities{"one": readCapability, "two": readCapability}, + want: pathCapabilities{"one": readCapability, "two": readCapability}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got := tt.have.missing(tt.require) + assertDifferentMaps(t, tt.have, tt.require, got) + assert.Equalf(tt.want, got, "pathCapabilities: want: {%s} got: {%s}", tt.want, got) + }) + } +} + +func TestPathCapabilities_String(t *testing.T) { + tests := []struct { + name string + pc pathCapabilities + contains []string + }{ + { + name: "empty-empty", + }, + { + name: "one-path", + pc: pathCapabilities{"one": createCapability}, + contains: []string{"one: create"}, + }, + { + name: "one-path-multiple-capabilities", + pc: pathCapabilities{"one": createCapability | readCapability | updateCapability}, + contains: []string{"one: create|read|update"}, + }, + { + name: "multiple-paths", + pc: pathCapabilities{"one": createCapability, "two": updateCapability}, + contains: []string{"one: create", "two: update"}, + }, + { + name: "multiple-paths-multiple-capabilities", + pc: pathCapabilities{ + "one": createCapability, + "two": createCapability | readCapability | updateCapability, + "three": readCapability | deleteCapability, + }, + contains: []string{"one: create", "two: create|read|update", "three: read|delete"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + got := tt.pc.String() + for _, s := range tt.contains { + assert.Containsf(got, s, "expected %q to contain %s", got, s) + } + }) + } +} + +func TestCapabilities_missing(t *testing.T) { + tests := []struct { + have, require, want capabilities + }{ + { + have: noCapabilities, + require: noCapabilities, + want: noCapabilities, + }, + { + have: createCapability, + require: createCapability, + }, + { + have: createCapability, + require: createCapability | listCapability, + want: listCapability, + }, + { + have: createCapability | readCapability, + require: createCapability | listCapability, + want: listCapability, + }, + { + have: createCapability | readCapability, + require: deleteCapability | listCapability, + want: deleteCapability | listCapability, + }, + } + for _, tt := range tests { + tt := tt + name := fmt.Sprintf("{%s} - {%s} = {%s}", tt.require, tt.have, tt.want) + t.Run(name, func(t *testing.T) { + assert := assert.New(t) + got := tt.have.missing(tt.require) + assert.Equalf(tt.want, got, "capabilities: want: {%s} got: {%s}", tt.want, got) + }) + } +} + +func TestCapabilities_String(t *testing.T) { + tests := []struct { + name string + given capabilities + want string + }{ + {"empty", noCapabilities, "[]"}, + {"one", createCapability, `["create"]`}, + {"two", createCapability | updateCapability, `["create", "update"]`}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got := tt.given.String() + if got != tt.want { + t.Errorf("(%s): want %s, got %s", tt.given, tt.want, got) + } + }) + } +} diff --git a/internal/credential/vault/vault_test.go b/internal/credential/vault/vault_test.go new file mode 100644 index 000000000..2852657f9 --- /dev/null +++ b/internal/credential/vault/vault_test.go @@ -0,0 +1,236 @@ +package vault + +import ( + "fmt" + "net/http" + "path" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/errors" + vault "github.com/hashicorp/vault/api" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_newClient(t *testing.T) { + t.Parallel() + t.Run("nilConfig", func(t *testing.T) { + assert := assert.New(t) + var c *clientConfig + client, err := newClient(c) + assert.Error(err) + assert.Nil(client) + }) +} + +func TestClient_RenewToken(t *testing.T) { + t.Parallel() + assert := assert.New(t) + v := NewTestVaultServer(t) + + _, token := v.CreateToken(t) + secretLookup := v.LookupToken(t, token) + + // need to sleep so the expiration times will be different + time.Sleep(100 * time.Millisecond) + + client := v.clientUsingToken(t, token) + renewedToken, err := client.renewToken() + require.NoError(t, err) + assert.NotNil(renewedToken) + + renewedLookup := v.LookupToken(t, token) + t1, t2 := tokenExpirationTime(t, secretLookup), tokenExpirationTime(t, renewedLookup) + assert.False(t1.Equal(t2)) + assert.True(t2.After(t1)) +} + +func tokenExpirationTime(t *testing.T, s *vault.Secret) time.Time { + t.Helper() + require := require.New(t) + + require.NotNil(s) + d1 := s.Data + require.NotNil(d1) + exp := d1["expire_time"] + require.NotEmpty(exp) + et, err := time.Parse(time.RFC3339, exp.(string)) + require.NoError(err) + return et +} + +func TestClient_LookupToken(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t) + + _, token := v.CreateToken(t) + secretLookup := v.LookupToken(t, token) + + client := v.clientUsingToken(t, token) + tokenLookup, err := client.lookupToken() + assert.NoError(err) + require.NotNil(tokenLookup) + + t1, t2 := tokenExpirationTime(t, secretLookup), tokenExpirationTime(t, tokenLookup) + assert.True(t1.Equal(t2)) +} + +func TestClient_RevokeToken(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t) + + _, token := v.CreateToken(t) + + client := v.clientUsingToken(t, token) + tokenLookup, err := client.lookupToken() + assert.NoError(err) + assert.NotNil(tokenLookup) + + require.NoError(client.revokeToken()) + + // An attempt to lookup should now fail with a 403 + tokenLookup, err = client.lookupToken() + require.Error(err) + + var respErr *vault.ResponseError + ok := errors.As(err, &respErr) + require.True(ok) + assert.Equal(http.StatusForbidden, respErr.StatusCode) +} + +func TestClient_Get(t *testing.T) { + t.Parallel() + assert := assert.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + client := v.client(t) + + cred, err := client.get(path.Join("database", "creds", "opened")) + assert.NoError(err) + assert.NotNil(cred) +} + +func TestClient_Post(t *testing.T) { + t.Parallel() + v := NewTestVaultServer(t, WithTestVaultTLS(TestServerTLS)) + v.MountPKI(t) + client := v.client(t) + credPath := path.Join("pki", "issue", "boundary") + t.Run("post-body", func(t *testing.T) { + assert := assert.New(t) + credData := []byte(`{"common_name":"boundary.com"}`) + cred, err := client.post(credPath, credData) + assert.NoError(err) + assert.NotNil(cred) + }) + t.Run("nil-body", func(t *testing.T) { + assert := assert.New(t) + cred, err := client.post(credPath, nil) + assert.Error(err) + assert.Contains(err.Error(), "common_name field is required") + assert.Nil(cred) + }) +} + +func TestClient_RenewLease(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true)) + v.MountDatabase(t) + + _, token := v.CreateToken(t, WithPolicies([]string{"boundary-controller", "database"})) + client := v.clientUsingToken(t, token) + + // Create secret + cred, err := client.get(path.Join("database", "creds", "opened")) + assert.NoError(err) + require.NotNil(cred) + + leaseLookup := v.LookupLease(t, cred.LeaseID) + require.NotNil(leaseLookup) + require.NotNil(leaseLookup.Data) + + // Verify lease has not been renewed + assert.Empty(leaseLookup.Data["last_renewal"]) + + renewedLease, err := client.renewLease(cred.LeaseID, time.Hour) + assert.NoError(err) + require.NotNil(renewedLease) + assert.Equal(cred.LeaseID, renewedLease.LeaseID) + + leaseLookup = v.LookupLease(t, cred.LeaseID) + require.NotNil(leaseLookup) + require.NotNil(leaseLookup.Data) + + // Verify lease been renewed + assert.NotEmpty(leaseLookup.Data["last_renewal"]) +} + +func TestClient_capabilities(t *testing.T) { + t.Parallel() + v := NewTestVaultServer(t) + + tests := []struct { + name string + polices []string + require pathCapabilities + wantMissing pathCapabilities + }{ + { + polices: []string{"default"}, + require: requiredCapabilities, + wantMissing: pathCapabilities{"sys/leases/revoke": updateCapability}, + }, + { + polices: []string{"default", "boundary-controller"}, + require: requiredCapabilities, + }, + { + polices: []string{"boundary-controller"}, + require: requiredCapabilities, + }, + } + for _, tt := range tests { + tt := tt + if tt.name == "" { + tt.name = fmt.Sprintf("%v", tt.polices) + } + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + _, token := v.CreateToken(t, WithPolicies(tt.polices)) + client := v.clientUsingToken(t, token) + + have, err := client.capabilities(tt.require.paths()) + assert.NoError(err) + got := have.missing(tt.require) + assert.Equalf(tt.wantMissing, got, "pathCapabilities: want: {%s} got: {%s}", tt.wantMissing, got) + }) + } +} + +func TestClient_revokeLease(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + v := NewTestVaultServer(t, WithDockerNetwork(true), WithTestVaultTLS(TestClientTLS)) + testDatabase := v.MountDatabase(t) + + _, token := v.CreateToken(t, WithPolicies([]string{"boundary-controller", "database"})) + client := v.clientUsingToken(t, token) + + cred, err := client.get(path.Join("database", "creds", "opened")) + assert.NoError(err) + require.NotNil(cred) + + // verify the database credentials work + assert.NoError(testDatabase.ValidateCredential(t, cred)) + + // revoke the database credentials + assert.NoError(client.revokeLease(cred.LeaseID)) + + // verify the database credentials no longer work + assert.Error(testDatabase.ValidateCredential(t, cred)) +} diff --git a/internal/credential/vault/vault_token.go b/internal/credential/vault/vault_token.go new file mode 100644 index 000000000..53f18234a --- /dev/null +++ b/internal/credential/vault/vault_token.go @@ -0,0 +1,195 @@ +package vault + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "time" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/hashicorp/go-kms-wrapping/structwrapping" + "golang.org/x/crypto/blake2b" + "google.golang.org/protobuf/proto" +) + +// A TokenStatus represents the status of a vault token. +type TokenStatus string + +const ( + // CurrentToken represents a vault token for a credential store that is + // used for retrieving credentials. Tokens in this state are renewed + // before they expire. A credential store can have only one current + // token. + CurrentToken TokenStatus = "current" + + // MaintainingToken represents a vault token that is no longer being + // used for retrieving credentials but is being renewed because it was + // used to retrieve credentials which are still being used in a + // session. After the dependent sessions are terminated, the token is + // revoked in Vault and the status transitions to RevokedToken. but is + // no longer being used for retrieving credentials. + MaintainingToken TokenStatus = "maintaining" + + // RevokeToken represents a token that should be revoked. + RevokeToken TokenStatus = "revoke" + + // RevokedToken represents a token that has been revoked. This is a + // terminal status. It does not transition to ExpiredToken. + RevokedToken TokenStatus = "revoked" + + // ExpiredToken represents a token that expired. This is a terminal + // status. It does not transition to RevokedToken. + ExpiredToken TokenStatus = "expired" +) + +// Token contains a vault token. It is owned by a credential store. +type Token struct { + *store.Token + tableName string `gorm:"-"` + expiration time.Duration `gorm:"-"` +} + +func newToken(storeId string, token TokenSecret, accessor []byte, expiration time.Duration) (*Token, error) { + const op = "vault.newToken" + if storeId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no store id") + } + if len(token) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no vault token") + } + if len(accessor) == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no vault token accessor") + } + if expiration == 0 { + return nil, errors.New(errors.InvalidParameter, op, "no expiration") + } + + tokenCopy := make(TokenSecret, len(token)) + copy(tokenCopy, token) + accessorCopy := make([]byte, len(accessor)) + copy(accessorCopy, accessor) + + key := blake2b.Sum256(accessorCopy) + mac := hmac.New(sha256.New, key[:]) + _, _ = mac.Write(tokenCopy) + hmac := mac.Sum(nil) + + t := &Token{ + expiration: expiration.Round(time.Second), + Token: &store.Token{ + StoreId: storeId, + TokenHmac: hmac, + Token: tokenCopy, + Status: string(CurrentToken), + }, + } + return t, nil +} + +func allocToken() *Token { + return &Token{ + Token: &store.Token{}, + } +} + +func (t *Token) clone() *Token { + cp := proto.Clone(t.Token) + return &Token{ + expiration: t.expiration, + Token: cp.(*store.Token), + } +} + +// TableName returns the table name. +func (t *Token) TableName() string { + if t.tableName != "" { + return t.tableName + } + return "credential_vault_token" +} + +// SetTableName sets the table name. +func (t *Token) SetTableName(n string) { + t.tableName = n +} + +func (t *Token) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(Token).encrypt" + if err := structwrapping.WrapStruct(ctx, cipher, t.Token, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Encrypt)) + } + t.KeyId = cipher.KeyID() + return nil +} + +func (t *Token) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "vault.(Token).decrypt" + if err := structwrapping.UnwrapStruct(ctx, cipher, t.Token, nil); err != nil { + return errors.Wrap(err, op, errors.WithCode(errors.Decrypt)) + } + return nil +} + +func (t *Token) insertQuery() (query string, queryValues []interface{}) { + query = insertTokenQuery + + exp := int(t.expiration.Round(time.Second).Seconds()) + queryValues = []interface{}{ + t.TokenHmac, + t.CtToken, + t.StoreId, + t.KeyId, + t.Status, + "now()", + exp, + } + return +} + +func (t *Token) updateStatusQuery(status TokenStatus) (query string, queryValues []interface{}) { + query = updateTokenStatusQuery + + queryValues = []interface{}{ + status, + t.TokenHmac, + } + return +} + +func (t *Token) updateExpirationQuery() (query string, queryValues []interface{}) { + query = updateTokenExpirationQuery + + exp := int(t.expiration.Round(time.Second).Seconds()) + queryValues = []interface{}{ + exp, + t.TokenHmac, + } + return +} + +func (t *Token) oplogMessage(opType db.OpType) *oplog.Message { + msg := oplog.Message{ + Message: t.clone(), + TypeName: t.TableName(), + } + switch opType { + case db.CreateOp: + msg.OpType = oplog.OpType_OP_TYPE_CREATE + case db.UpdateOp: + msg.OpType = oplog.OpType_OP_TYPE_UPDATE + // msg.FieldMaskPaths = opts.WithFieldMaskPaths + // msg.SetToNullPaths = opts.WithNullPaths + case db.DeleteOp: + msg.OpType = oplog.OpType_OP_TYPE_DELETE + } + return &msg +} + +func (t *Token) renewalIn() time.Duration { + // Token renewal should be attempted half way to expiration + return t.expiration / 2 +} diff --git a/internal/credential/vault/vault_token_test.go b/internal/credential/vault/vault_token_test.go new file mode 100644 index 000000000..106531f67 --- /dev/null +++ b/internal/credential/vault/vault_token_test.go @@ -0,0 +1,151 @@ +package vault + +import ( + "context" + "crypto/hmac" + "crypto/sha256" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/blake2b" +) + +func TestToken_New(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + + kkms := kms.TestKms(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStores(t, conn, wrapper, prj.PublicId, 1)[0] + + type args struct { + storeId string + token TokenSecret + accessor []byte + expiration time.Duration + } + + hmac := func(t, a []byte) []byte { + key := blake2b.Sum256(a) + mac := hmac.New(sha256.New, key[:]) + _, _ = mac.Write(t) + return mac.Sum(nil) + } + + tests := []struct { + name string + args args + want *Token + wantErr bool + }{ + { + name: "blank-store-id", + args: args{ + storeId: "", + token: TokenSecret("token"), + accessor: []byte("accessor"), + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "missing-token", + args: args{ + storeId: cs.PublicId, + accessor: []byte("accessor"), + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "missing-accessor", + args: args{ + storeId: cs.PublicId, + token: TokenSecret("token"), + expiration: 5 * time.Minute, + }, + want: nil, + wantErr: true, + }, + { + name: "missing-expiration", + args: args{ + storeId: cs.PublicId, + token: TokenSecret("token"), + accessor: []byte("accessor"), + }, + want: nil, + wantErr: true, + }, + { + name: "valid", + args: args{ + storeId: cs.PublicId, + token: TokenSecret("token"), + accessor: []byte("accessor"), + expiration: 5 * time.Minute, + }, + want: &Token{ + Token: &store.Token{ + StoreId: cs.PublicId, + Token: []byte("token"), + TokenHmac: hmac([]byte("token"), []byte("accessor")), + Status: string(CurrentToken), + }, + expiration: 5 * time.Minute, + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + ctx := context.Background() + databaseWrapper, err := kkms.GetWrapper(ctx, prj.PublicId, kms.KeyPurposeDatabase) + require.NoError(err) + require.NotNil(databaseWrapper) + + got, err := newToken(tt.args.storeId, tt.args.token, tt.args.accessor, tt.args.expiration) + if tt.wantErr { + assert.Error(err) + require.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + + want := tt.want + assert.Empty(got.CtToken) + assert.Equal(want, got) + + require.NoError(got.encrypt(ctx, databaseWrapper)) + require.NoError(got.decrypt(ctx, databaseWrapper)) + }) + } +} + +func subtract(t *testing.T, startTime, endTime *timestamp.Timestamp) time.Duration { + t.Helper() + require := require.New(t) + require.NotNil(startTime, "startTime nil") + require.NotNil(endTime, "endTime nil") + + a, b := startTime.GetTimestamp().AsTime(), endTime.GetTimestamp().AsTime() + if b.After(a) { + a, b = b, a + } + return a.Sub(b) +} diff --git a/internal/db/db.go b/internal/db/db.go index 64c4fff45..3ef969a15 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -2,6 +2,8 @@ package db import ( "fmt" + "math" + "time" "github.com/hashicorp/boundary/internal/docker" "github.com/hashicorp/go-hclog" @@ -9,6 +11,15 @@ import ( "github.com/lib/pq" ) +var ( + NegativeInfinityTS = time.Date(math.MinInt32, time.January, 1, 0, 0, 0, 0, time.UTC) + PositiveInfinityTS = time.Date(math.MaxInt32, time.December, 31, 23, 59, 59, 1e9-1, time.UTC) +) + +func init() { + pq.EnableInfinityTs(NegativeInfinityTS, PositiveInfinityTS) +} + var StartDbInDocker = docker.StartDbInDocker type DbType int diff --git a/internal/db/db_test.go b/internal/db/db_test.go index f3f762d45..75fc50758 100644 --- a/internal/db/db_test.go +++ b/internal/db/db_test.go @@ -2,6 +2,9 @@ package db import ( "testing" + + "github.com/lib/pq" + "github.com/stretchr/testify/assert" ) func TestOpen(t *testing.T) { @@ -58,3 +61,9 @@ func TestOpen(t *testing.T) { }) } } + +func TestPostgresInfinity(t *testing.T) { + assert.Panics(t, func() { + pq.EnableInfinityTs(NegativeInfinityTS, PositiveInfinityTS) + }) +} diff --git a/internal/db/domains_test.go b/internal/db/domains_test.go index 9e02e389f..0673fa1dd 100644 --- a/internal/db/domains_test.go +++ b/internal/db/domains_test.go @@ -899,3 +899,226 @@ returning id; }) } } + +func TestDomain_wt_sentinel(t *testing.T) { + const ( + createTable = ` +create table if not exists test_table_wt_sentinel ( + id bigint generated always as identity primary key, + sentinel wt_sentinel +); +` + insert = ` +insert into test_table_wt_sentinel(sentinel) +values ($1) +returning id; +` + ) + + conn, _ := TestSetup(t, "postgres") + db := conn.DB() + + if _, err := db.Exec(createTable); err != nil { + t.Fatalf("query: \n%s\n error: %s", createTable, err) + } + + tests := []struct { + name string + value string + wantErr bool + }{ + {"normal", "\ufffefoo", false}, + {"normal non-sentinel", "foo", false}, + {"trailing sentinel", "\ufffefoo\ufffe", false}, + {"sentinel with space before word", "\ufffe foo", false}, + {"sentinel with empty string", "\ufffe ", true}, + {"multiple sentinels with empty string", "\ufffe\ufffe ", true}, + {"multiple sentinels", "\ufffe\ufffefoo", false}, + {"sentinel space sentinel space string", "\ufffe \ufffe foo ", false}, + {"empty string", " ", true}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert := assert.New(t) + t.Logf("insert value: %q", tt.value) + _, err := db.Query(insert, tt.value) + if tt.wantErr { + assert.Error(err) + return + } + assert.NoError(err) + }) + } +} + +func TestDomain_wt_is_sentinel(t *testing.T) { + const ( + query = ` +select wt_is_sentinel($1); +` + ) + + conn, _ := TestSetup(t, "postgres") + db := conn.DB() + + tests := []struct { + name string + value string + want bool + }{ + {"normal", "\ufffefoo", true}, + {"non-sentinel", "foo", false}, + {"trailing sentinel", "\ufffefoo\ufffe", true}, + {"sentinel with space before word", "\ufffe foo", true}, + {"sentinel with empty string", "\ufffe ", false}, + {"multiple sentinels with empty string", "\ufffe\ufffe ", false}, + {"multiple sentinels", "\ufffe\ufffefoo", true}, + {"sentinel space sentinel space string", "\ufffe \ufffe foo ", true}, + {"empty string", " ", false}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + t.Logf("query value: %q", tt.value) + + var got bool + rows, err := db.Query(query, tt.value) + require.NoError(err) + defer rows.Close() + + require.True(rows.Next()) + require.NoError(rows.Scan(&got)) + assert.Equal(tt.want, got) + + require.False(rows.Next()) + require.NoError(rows.Err()) + }) + } +} + +func TestDomain_wt_to_sentinel(t *testing.T) { + const ( + query = ` +select wt_to_sentinel($1); +` + ) + + conn, _ := TestSetup(t, "postgres") + db := conn.DB() + + tests := []struct { + name string + value string + want string + }{ + {"V", "foo", "\ufffefoo"}, + {"space V", " foo", "\ufffefoo"}, + {"V space", "foo ", "\ufffefoo"}, + {"space space V", " foo ", "\ufffefoo"}, + {"sentinel V", "\ufffefoo", "\ufffefoo"}, + {"sentinel space V", "\ufffe foo", "\ufffefoo"}, + {"sentinel V space", "\ufffefoo ", "\ufffefoo"}, + {"sentinel sentinel V", "\ufffe\ufffefoo", "\ufffefoo"}, + {"sentinel sentinel space V", "\ufffe\ufffe foo", "\ufffefoo"}, + {"sentinel sentinel V space", "\ufffe\ufffefoo ", "\ufffefoo"}, + {"sentinel space sentinel space V space", "\ufffe \ufffe foo ", "\ufffefoo"}, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + t.Logf("query value: %q", tt.value) + + var got string + rows, err := db.Query(query, tt.value) + require.NoError(err) + defer rows.Close() + + require.True(rows.Next()) + require.NoError(rows.Scan(&got)) + assert.Equal(tt.want, got) + + require.False(rows.Next()) + require.NoError(rows.Err()) + }) + } +} + +func TestDomain_not_null_columns_func(t *testing.T) { + const ( + createTable = ` +create table if not exists test_not_null_columns ( + id bigint generated always as identity primary key, + one text, + two text, + three text +); +` + addTriggers = ` +create trigger + test_insert_not_null_columns +before +insert on test_not_null_columns + for each row execute procedure not_null_columns('one', 'two'); +` + dropTriggers = `drop trigger test_insert_not_null_columns on test_not_null_columns` + ) + + conn, _ := TestSetup(t, "postgres") + db := conn.DB() + _, err := db.Exec(createTable) + assert.NoError(t, err) + + tests := []struct { + name string + stmt string + wantErr bool + }{ + { + name: "null one", + stmt: "insert into test_not_null_columns (one, two, three) values (null, 'two', 'three');", + wantErr: true, + }, + { + name: "null two", + stmt: "insert into test_not_null_columns (one, two, three) values ('one', null, 'three');", + wantErr: true, + }, + { + name: "all null", + stmt: "insert into test_not_null_columns (one, two, three) values (null, null, null);", + wantErr: true, + }, + { + name: "null three", + stmt: "insert into test_not_null_columns (one, two, three) values ('one', 'two', null);", + wantErr: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + require, assert := require.New(t), assert.New(t) + + _, err = db.Exec(addTriggers) + require.NoError(err) + + _, err = db.Exec(tt.stmt) + + if !tt.wantErr { + assert.NoError(err) + return + } + require.Error(err) + + // Drop trigger and run insert again, it should now insert successfully + _, err = db.Exec(dropTriggers) + assert.NoError(err) + + _, err = db.Exec(tt.stmt) + assert.NoError(err) + }) + } +} diff --git a/internal/db/schema/migrations/postgres/10/02_sentinels.up.sql b/internal/db/schema/migrations/postgres/10/02_sentinels.up.sql new file mode 100644 index 000000000..b571ea0ad --- /dev/null +++ b/internal/db/schema/migrations/postgres/10/02_sentinels.up.sql @@ -0,0 +1,33 @@ +begin; + + create function wt_is_sentinel(string text) + returns bool + as $$ + select length(trim(leading u&'\fffe ' from string)) > 0 AND starts_with(string, u&'\fffe'); + $$ language sql + immutable + returns null on null input; + comment on function wt_is_sentinel is + 'wt_is_sentinel returns true if string is a sentinel value'; + + create domain wt_sentinel as text + constraint wt_sentinel_not_valid + check( + wt_is_sentinel(value) + or + length(trim(u&'\fffe ' from value)) > 0 + ); + comment on domain wt_sentinel is + 'A non-empty string with a Unicode prefix of U+FFFE to indicate it is a sentinel value'; + + create function wt_to_sentinel(string text) + returns text + as $$ + select concat(u&'\fffe', trim(ltrim(string, u&'\fffe '))); + $$ language sql + immutable + returns null on null input; + comment on function wt_to_sentinel is + 'wt_to_sentinel takes string and returns it as a wt_sentinel'; + +commit; diff --git a/internal/db/schema/migrations/postgres/10/03_credential.up.sql b/internal/db/schema/migrations/postgres/10/03_credential.up.sql new file mode 100644 index 000000000..5610cb89f --- /dev/null +++ b/internal/db/schema/migrations/postgres/10/03_credential.up.sql @@ -0,0 +1,261 @@ +begin; + + -- credential_store + create table credential_store ( + public_id wt_public_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_fkey + references iam_scope (public_id) + on delete cascade + on update cascade, + + -- The order of columns is important for performance. See: + -- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972 + -- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field + constraint credential_store_scope_id_public_id_uq + unique(scope_id, public_id) + ); + comment on table credential_store is + 'credential_store is a base table for the credential store type. ' + 'Each row is owned by a single scope and maps 1-to-1 to a row in one of the credential store subtype tables.'; + + create trigger immutable_columns before update on credential_store + for each row execute procedure immutable_columns('public_id', 'scope_id'); + + -- insert_credential_store_subtype() is a before insert trigger + -- function for subtypes of credential_store + create function insert_credential_store_subtype() + returns trigger + as $$ + begin + insert into credential_store + (public_id, scope_id) + values + (new.public_id, new.scope_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_store_subtype() is an after delete trigger + -- function for subtypes of credential_store + create function delete_credential_store_subtype() + returns trigger + as $$ + begin + delete from credential_store + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + + -- credential_library + create table credential_library ( + public_id wt_public_id primary key, + store_id wt_public_id not null + constraint credential_store_fkey + references credential_store (public_id) + on delete cascade + on update cascade, + constraint credential_library_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_library is + 'credential_library is a base table for the credential library type and a child table of credential_store. ' + 'Each row maps 1-to-1 to a row in one of the credential library subtype tables.'; + + create trigger immutable_columns before update on credential_library + for each row execute procedure immutable_columns('public_id', 'store_id'); + + -- insert_credential_library_subtype() is a before insert trigger + -- function for subtypes of credential_library + create function insert_credential_library_subtype() + returns trigger + as $$ + begin + insert into credential_library + (public_id, store_id) + values + (new.public_id, new.store_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_library_subtype() is an after delete trigger + -- function for subtypes of credential_library + create function delete_credential_library_subtype() + returns trigger + as $$ + begin + delete from credential_library + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential + create table credential ( + public_id wt_public_id primary key + ); + comment on table credential is + 'credential is a base table for the credential type. '; + + create trigger immutable_columns before update on credential + for each row execute procedure immutable_columns('public_id'); + + -- insert_credential_subtype() is a before insert trigger + -- function for subtypes of credential + create function insert_credential_subtype() + returns trigger + as $$ + begin + insert into credential + (public_id) + values + (new.public_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_subtype() is an after delete trigger + -- function for subtypes of credential + create function delete_credential_subtype() + returns trigger + as $$ + begin + delete from credential + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential_static + create table credential_static ( + public_id wt_public_id primary key + constraint credential_fkey + references credential (public_id) + on delete cascade + on update cascade, + store_id wt_public_id not null + constraint credential_store_fkey + references credential_store (public_id) + on delete cascade + on update cascade, + constraint credential_static_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_static is + 'credential_static is a base table for the credential static type. ' + 'It is a credential subtype and a child table of credential_store. '; + + create trigger immutable_columns before update on credential_static + for each row execute procedure immutable_columns('public_id', 'store_id'); + + create trigger insert_credential_subtype before insert on credential_static + for each row execute procedure insert_credential_subtype(); + + create trigger delete_credential_subtype after delete on credential_static + for each row execute procedure delete_credential_subtype(); + + -- insert_credential_static_subtype() is a before insert trigger + -- function for subtypes of credential_static + create function insert_credential_static_subtype() + returns trigger + as $$ + begin + insert into credential_static + (public_id, store_id) + values + (new.public_id, new.store_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_static_subtype() is an after delete trigger + -- function for subtypes of credential_static + create function delete_credential_static_subtype() + returns trigger + as $$ + begin + delete from credential_static + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential_dynamic + create table credential_dynamic ( + public_id wt_public_id primary key + constraint credential_fkey + references credential (public_id) + on delete cascade + on update cascade, + library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + constraint credential_dynamic_library_id_public_id_uq + unique(library_id, public_id) + ); + comment on table credential_dynamic is + 'credential_dynamic is a base table for the credential dynamic type. ' + 'It is a credential subtype and a child table of credential_library. '; + + create trigger immutable_columns before update on credential_dynamic + for each row execute procedure immutable_columns('public_id', 'library_id'); + + create trigger insert_credential_subtype before insert on credential_dynamic + for each row execute procedure insert_credential_subtype(); + + create trigger delete_credential_subtype after delete on credential_dynamic + for each row execute procedure delete_credential_subtype(); + + -- insert_credential_dynamic_subtype() is a before insert trigger + -- function for subtypes of credential_dynamic + create function insert_credential_dynamic_subtype() + returns trigger + as $$ + begin + insert into credential_dynamic + (public_id, library_id) + values + (new.public_id, new.library_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_dynamic_subtype() is an after delete trigger + -- function for subtypes of credential_dynamic + create function delete_credential_dynamic_subtype() + returns trigger + as $$ + begin + delete from credential_dynamic + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + create table credential_purpose_enm ( + name text primary key + constraint only_predefined_credential_purposes_allowed + check ( + name in ( + 'application', + 'ingress', + 'egress' + ) + ) + ); + comment on table credential_purpose_enm is + 'credential_purpose_enm is an enumeration table for credential purposes. ' + 'It contains rows for representing the application, egress, and ingress credential purposes.'; + + insert into credential_purpose_enm (name) + values + ('application'), + ('ingress'), + ('egress'); + +commit; diff --git a/internal/db/schema/migrations/postgres/10/04_vault_credential.up.sql b/internal/db/schema/migrations/postgres/10/04_vault_credential.up.sql new file mode 100644 index 000000000..f1ecbb2a6 --- /dev/null +++ b/internal/db/schema/migrations/postgres/10/04_vault_credential.up.sql @@ -0,0 +1,655 @@ +begin; + + create table credential_vault_store ( + public_id wt_public_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_fkey + references iam_scope (public_id) + on delete cascade + on update cascade, + name wt_name, + description wt_description, + create_time wt_timestamp, + update_time wt_timestamp, + -- delete_time is set to indicate the row has been soft deleted + delete_time timestamp with time zone, + version wt_version, + vault_address wt_url not null, + -- the remaining text columns can be null but if they are not null, they + -- cannot contain an empty string + namespace text + constraint namespace_must_not_be_empty + check(length(trim(namespace)) > 0), + ca_cert bytea -- PEM encoded certificate bundle + constraint ca_cert_must_not_be_empty + check(length(ca_cert) > 0), + tls_server_name text + constraint tls_server_name_must_not_be_empty + check(length(trim(tls_server_name)) > 0), + tls_skip_verify boolean default false not null, + constraint credential_store_fkey + foreign key (scope_id, public_id) + references credential_store (scope_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_store_scope_id_name_uq + unique(scope_id, name) + ); + comment on table credential_vault_store is + 'credential_vault_store is a table where each row is a resource that represents a vault credential store. ' + 'It is a credential_store subtype.'; + + create trigger update_version_column after update on credential_vault_store + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_store + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_store + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_store + for each row execute procedure immutable_columns('public_id', 'scope_id','create_time'); + + create trigger insert_credential_store_subtype before insert on credential_vault_store + for each row execute procedure insert_credential_store_subtype(); + + create trigger delete_credential_store_subtype after delete on credential_vault_store + for each row execute procedure delete_credential_store_subtype(); + + -- before_soft_delete_credential_vault_store is a before update trigger for + -- the credential_vault_store table that makes the delete_time column a + -- set once column. Once the delete_time column is set to a value other than + -- null, it cannot be changed. If the current delete_time of a row is not null + -- and an update contains a value for delete_time different from the current + -- value, this trigger will raise an error with error code 23602 which is a + -- class 23 integrity constraint violation: set_once_violation. + create function before_soft_delete_credential_vault_store() + returns trigger + as $$ + begin + if new.delete_time is distinct from old.delete_time then + if old.delete_time is not null then + raise exception 'set_once_violation: %.%', tg_table_name, 'delete_time' using + errcode = '23602', + schema = tg_table_schema, + table = tg_table_name, + column = 'delete_time'; + end if; + end if; + return new; + end; + $$ language plpgsql; + + create trigger before_soft_delete_credential_vault_store before update on credential_vault_store + for each row execute procedure before_soft_delete_credential_vault_store(); + + -- after_soft_delete_credential_vault_store is an after update trigger for the + -- credential_vault_store table that performs cleanup actions when a + -- credential store is soft deleted. A credential store is considered "soft + -- deleted" if the delete_time for the row is updated from null to not null. + -- + -- When a credential store is soft deleted, this trigger: + -- * marks any active Vault tokens owned by the credential store for revocation + -- * deletes any credential library owned by the credential store + create function after_soft_delete_credential_vault_store() + returns trigger + as $$ + begin + if new.delete_time is distinct from old.delete_time then + if old.delete_time is null then + + -- mark current and maintaining tokens as revoke + update credential_vault_token + set status = 'revoke' + where store_id = new.public_id + and status in ('current', 'maintaining'); + + -- delete the store's libraries + delete + from credential_vault_library + where store_id = new.public_id; + + end if; + end if; + return null; + end; + $$ language plpgsql; + + create trigger after_soft_delete_credential_vault_store after update on credential_vault_store + for each row execute procedure after_soft_delete_credential_vault_store(); + + create table credential_vault_token_status_enm ( + name text primary key + constraint only_predefined_token_statuses_allowed + check ( + name in ( + 'current', + 'maintaining', + 'revoke', + 'revoked', + 'expired' + ) + ) + ); + comment on table credential_vault_token_status_enm is + 'credential_vault_token_status_enm is an enumeration table for the status of vault tokens. ' + 'It contains rows for representing the current, maintaining, revoke, revoked, and expired statuses.'; + + insert into credential_vault_token_status_enm (name) + values + ('current'), + ('maintaining'), + ('revoke'), + ('revoked'), + ('expired'); + + create table credential_vault_token ( + token_hmac bytea primary key, -- hmac-sha256(token, key(blake2b-256(token_accessor)) + token bytea not null, -- encrypted value + store_id wt_public_id not null + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + create_time wt_timestamp, + update_time wt_timestamp, + last_renewal_time timestamp with time zone not null, + expiration_time timestamp with time zone not null + constraint last_renewal_time_must_be_before_expiration_time + check(last_renewal_time < expiration_time), + key_id text not null + constraint kms_database_key_version_fkey + references kms_database_key_version (private_id) + on delete restrict + on update cascade, + status text not null + constraint credential_vault_token_status_enm_fkey + references credential_vault_token_status_enm (name) + on delete restrict + on update cascade + ); + comment on table credential_vault_token is + 'credential_vault_token is a table where each row contains a Vault token for one Vault credential store. ' + 'A credential store can have only one vault token with the status of current'; + comment on column credential_vault_token.token_hmac is + 'token_hmac contains the hmac-sha256 value of the token. ' + 'The hmac key is the blake2b-256 value of the token accessor.'; + + -- https://www.postgresql.org/docs/current/indexes-partial.html + create unique index credential_vault_token_current_status_constraint + on credential_vault_token (store_id) + where status = 'current'; + + create index credential_vault_token_expiration_time_ix + on credential_vault_token(expiration_time); + comment on index credential_vault_token_expiration_time_ix is + 'the credential_vault_token_expiration_time_ix is used by the token renewal job'; + + create trigger update_time_column before update on credential_vault_token + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_token + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_token + for each row execute procedure immutable_columns('token_hmac', 'token', 'store_id','create_time'); + + -- insert_credential_vault_token() is a before insert trigger + -- function for credential_vault_token that changes the status of the current + -- token to 'maintaining' + create function insert_credential_vault_token() + returns trigger + as $$ + begin + update credential_vault_token + set status = 'maintaining' + where store_id = new.store_id + and status = 'current'; + return new; + end; + $$ language plpgsql; + + create trigger insert_credential_vault_token before insert on credential_vault_token + for each row execute procedure insert_credential_vault_token(); + + create table credential_vault_client_certificate ( + store_id wt_public_id primary key + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + certificate bytea not null -- PEM encoded certificate + constraint certificate_must_not_be_empty + check(length(certificate) > 0), + certificate_key bytea not null -- encrypted PEM encoded private key for certificate + constraint certificate_key_must_not_be_empty + check(length(certificate_key) > 0), + certificate_key_hmac bytea not null + constraint certificate_key_hmac_must_not_be_empty + check(length(certificate_key_hmac) > 0), + key_id text not null + constraint kms_database_key_version_fkey + references kms_database_key_version (private_id) + on delete restrict + on update cascade + ); + comment on table credential_vault_client_certificate is + 'credential_vault_client_certificate is a table where each row contains a client certificate that a credential_vault_store uses for mTLS when connecting to Vault. ' + 'A credential_vault_store can have 0 or 1 client certificates.'; + + create trigger immutable_columns before update on credential_vault_client_certificate + for each row execute procedure immutable_columns('store_id'); + + create table credential_vault_http_method_enm ( + name text primary key + constraint only_predefined_http_methods_allowed + check ( + name in ( + 'GET', + 'POST' + ) + ) + ); + comment on table credential_vault_http_method_enm is + 'credential_vault_http_method_enm is an enumeration table for the http method used by a vault library when communicating with vault. ' + 'It contains rows for representing the HTTP GET and the HTTP POST methods.'; + + insert into credential_vault_http_method_enm (name) + values + ('GET'), + ('POST'); + + create table credential_vault_library ( + public_id wt_public_id primary key, + store_id wt_public_id not null + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + name wt_name, + description wt_description, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + vault_path text not null + constraint vault_path_must_not_be_empty + check(length(trim(vault_path)) > 0), + http_method text not null + constraint credential_vault_http_method_enm_fkey + references credential_vault_http_method_enm (name) + on delete restrict + on update cascade, + http_request_body bytea + constraint http_request_body_only_allowed_with_post_method + check( + http_request_body is null + or + ( + http_method = 'POST' + and + length(http_request_body) > 0 + ) + ), + constraint credential_vault_library_store_id_name_uq + unique(store_id, name), + constraint credential_library_fkey + foreign key (store_id, public_id) + references credential_library (store_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_library_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_vault_library is + 'credential_vault_library is a table where each row is a resource that represents a vault credential library. ' + 'It is a credential_library subtype and a child table of credential_vault_store.'; + + create trigger update_version_column after update on credential_vault_library + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_library + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_library + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_library + for each row execute procedure immutable_columns('public_id', 'store_id','create_time'); + + create trigger insert_credential_library_subtype before insert on credential_vault_library + for each row execute procedure insert_credential_library_subtype(); + + create trigger delete_credential_library_subtype after delete on credential_vault_library + for each row execute procedure delete_credential_library_subtype(); + + + -- before_insert_credential_vault_library is a before insert trigger for + -- the credential_vault_library table that prevents a library from being + -- inserted for a soft deleted credential store. + create function before_insert_credential_vault_library() + returns trigger + as $$ + declare + delete_time_val timestamp with time zone; + begin + select delete_time into delete_time_val + from credential_vault_store + where public_id = new.store_id; + + if delete_time_val is not null then + raise exception 'foreign_key_violation: %.%', tg_table_name, 'store_id' using + errcode = '23503', + schema = tg_table_schema, + table = tg_table_name, + column = 'store_id'; + end if; + return new; + end; + $$ language plpgsql; + + create trigger before_insert_credential_vault_library before insert on credential_vault_library + for each row execute procedure before_insert_credential_vault_library(); + + create table credential_vault_credential_status_enm ( + name text primary key + constraint only_predefined_credential_statuses_allowed + check ( + name in ( + 'active', + 'revoke', + 'revoked', + 'expired', + 'unknown' + ) + ) + ); + comment on table credential_vault_credential_status_enm is + 'credential_vault_credential_status_enm is an enumeration table for the status of vault credentials. ' + 'It contains rows for representing the active, revoke, revoked, expired, and unknown statuses.'; + + insert into credential_vault_credential_status_enm (name) + values + ('active'), + ('revoke'), + ('revoked'), + ('expired'), + ('unknown'); + + create table credential_vault_credential ( + public_id wt_public_id primary key, + library_id wt_public_id + constraint credential_vault_library_fkey + references credential_vault_library (public_id) + on delete set null + on update cascade, + session_id wt_public_id + constraint session_fkey + references session (public_id) + on delete set null + on update cascade, + token_hmac bytea not null + constraint credential_vault_token_fkey + references credential_vault_token (token_hmac) + on delete cascade + on update cascade, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + external_id wt_sentinel not null, + last_renewal_time timestamp with time zone not null, + expiration_time timestamp with time zone not null + constraint last_renewal_time_must_be_before_expiration_time + check(last_renewal_time < expiration_time), + is_renewable boolean not null, + status text not null + constraint credential_vault_credential_status_enm_fkey + references credential_vault_credential_status_enm (name) + on delete restrict + on update cascade, + constraint credential_dynamic_fkey + foreign key (library_id, public_id) + references credential_dynamic (library_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_credential_library_id_public_id_uq + unique(library_id, public_id) + ); + comment on table credential_vault_credential is + 'credential_vault_credential is a table where each row contains the lease information for a single Vault secret retrieved from a vault credential library for a session.'; + + create trigger update_version_column after update on credential_vault_credential + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_credential + for each row execute procedure update_time_column(); + + -- update_credential_status_column() is a before update trigger function for + -- credential_vault_credential that changes the status of the credential to 'revoke' if + -- the session_id is updated to null + create function update_credential_status_column() + returns trigger + as $$ + begin + if new.session_id is distinct from old.session_id then + if new.session_id is null and old.status = 'active' then + new.status = 'revoke'; + end if; + end if; + return new; + end; + $$ language plpgsql; + + create trigger update_credential_status_column before update on credential_vault_credential + for each row execute procedure update_credential_status_column(); + + -- not_null_columns() will make the column names not null which are passed as + -- parameters when the trigger is created. It raises error code 23502 which is a + -- class 23 integrity constraint violation: not null column + create function not_null_columns() + returns trigger + as $$ + declare + col_name text; + new_value text; + begin + foreach col_name in array tg_argv loop + execute format('SELECT $1.%I', col_name) into new_value using new; + if new_value is null then + raise exception 'not null column: %.%', tg_table_name, col_name using + errcode = '23502', + schema = tg_table_schema, + table = tg_table_name, + column = col_name; + end if; + end loop; + return new; + end; + $$ language plpgsql; + + comment on function not_null_columns() is + 'function used in before insert triggers to make columns not null on insert, but are allowed be updated to null'; + + create trigger not_null_columns before insert on credential_vault_credential + for each row execute procedure not_null_columns('library_id', 'session_id'); + + create trigger default_create_time_column before insert on credential_vault_credential + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_credential + for each row execute procedure immutable_columns('external_id', 'create_time'); + + create trigger insert_credential_dynamic_subtype before insert on credential_vault_credential + for each row execute procedure insert_credential_dynamic_subtype(); + + create trigger delete_credential_dynamic_subtype after delete on credential_vault_credential + for each row execute procedure delete_credential_dynamic_subtype(); + + create index credential_vault_credential_expiration_time_ix + on credential_vault_credential(expiration_time); + comment on index credential_vault_credential_expiration_time_ix is + 'the credential_vault_credential_expiration_time_ix is used by the credential renewal job'; + + insert into oplog_ticket (name, version) + values + ('credential_vault_store', 1), + ('credential_vault_library', 1), + ('credential_vault_credential', 1) ; + + create view credential_vault_store_private as + with + active_tokens as ( + select token_hmac, + token, -- encrypted + store_id, + create_time, + update_time, + last_renewal_time, + expiration_time, + -- renewal time is the midpoint between the last renewal time and the expiration time + last_renewal_time + (expiration_time - last_renewal_time) / 2 as renewal_time, + key_id, + status + from credential_vault_token + where status in ('current', 'maintaining', 'revoke') + ) + select store.public_id as public_id, + store.scope_id as scope_id, + store.name as name, + store.description as description, + store.create_time as create_time, + store.update_time as update_time, + store.delete_time as delete_time, + store.version as version, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.public_id as store_id, + token.token_hmac as token_hmac, + token.token as ct_token, -- encrypted + token.create_time as token_create_time, + token.update_time as token_update_time, + token.last_renewal_time as token_last_renewal_time, + token.expiration_time as token_expiration_time, + token.renewal_time as token_renewal_time, + token.key_id as token_key_id, + token.status as token_status, + cert.certificate as client_cert, + cert.certificate_key as ct_client_key, -- encrypted + cert.certificate_key_hmac as client_cert_key_hmac, + cert.key_id as client_key_id + from credential_vault_store store + left join active_tokens token + on store.public_id = token.store_id + left join credential_vault_client_certificate cert + on store.public_id = cert.store_id; + comment on view credential_vault_store_private is + 'credential_vault_store_private is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. ' + 'The view returns a separate row for each current, maintaining and revoke token; maintaining tokens should only be used for token/credential renewal and revocation. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_store_public as + select public_id, + scope_id, + name, + description, + create_time, + update_time, + version, + vault_address, + namespace, + ca_cert, + tls_server_name, + tls_skip_verify, + token_hmac, + token_create_time, + token_update_time, + token_last_renewal_time, + token_expiration_time, + client_cert, + client_cert_key_hmac + from credential_vault_store_private + where token_status = 'current' + and delete_time is null; + comment on view credential_vault_store_public is + 'credential_vault_store_public is a view where each row contains a credential store. ' + 'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_library_private as + select library.public_id as public_id, + library.store_id as store_id, + library.name as name, + library.description as description, + library.create_time as create_time, + library.update_time as update_time, + library.version as version, + library.vault_path as vault_path, + library.http_method as http_method, + library.http_request_body as http_request_body, + store.scope_id as scope_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.token_hmac as token_hmac, + store.ct_token as ct_token, -- encrypted + store.token_key_id as token_key_id, + store.client_cert as client_cert, + store.ct_client_key as ct_client_key, -- encrypted + store.client_key_id as client_key_id + from credential_vault_library library + join credential_vault_store_private store + on library.store_id = store.public_id + and store.token_status = 'current'; + comment on view credential_vault_library_private is + 'credential_vault_library_private is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_credential_private as + select credential.public_id as public_id, + credential.library_id as library_id, + credential.session_id as session_id, + credential.create_time as create_time, + credential.update_time as update_time, + credential.version as version, + credential.external_id as external_id, + credential.last_renewal_time as last_renewal_time, + credential.expiration_time as expiration_time, + credential.is_renewable as is_renewable, + credential.status as status, + credential.last_renewal_time + (credential.expiration_time - credential.last_renewal_time) / 2 as renewal_time, + token.token_hmac as token_hmac, + token.token as ct_token, -- encrypted + token.create_time as token_create_time, + token.update_time as token_update_time, + token.last_renewal_time as token_last_renewal_time, + token.expiration_time as token_expiration_time, + token.key_id as token_key_id, + token.status as token_status, + store.scope_id as scope_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + cert.certificate as client_cert, + cert.certificate_key as ct_client_key, -- encrypted + cert.certificate_key_hmac as client_cert_key_hmac, + cert.key_id as client_key_id + from credential_vault_credential credential + join credential_vault_token token + on credential.token_hmac = token.token_hmac + join credential_vault_store store + on token.store_id = store.public_id + left join credential_vault_client_certificate cert + on store.public_id = cert.store_id + where credential.expiration_time != 'infinity'::date; + comment on view credential_vault_credential_private is + 'credential_vault_credential_private is a view where each row contains a credential, ' + 'the vault token used to issue the credential, and the credential store data needed to connect to Vault. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; + +commit; diff --git a/internal/db/schema/migrations/postgres/10/05_target.up.sql b/internal/db/schema/migrations/postgres/10/05_target.up.sql new file mode 100644 index 000000000..58e4b97ce --- /dev/null +++ b/internal/db/schema/migrations/postgres/10/05_target.up.sql @@ -0,0 +1,47 @@ +begin; + + create table target_credential_library ( + target_id wt_public_id not null + constraint target_fkey + references target (public_id) + on delete cascade + on update cascade, + credential_library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + credential_purpose text not null + constraint credential_purpose_enm_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + create_time wt_timestamp, + primary key(target_id, credential_library_id, credential_purpose) + ); + comment on table target_credential_library is + 'target_credential_library is a join table between the target and credential_library tables. ' + 'It also contains the credential purpose the relationship represents.'; + + create trigger default_create_time_column before insert on target_credential_library + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on target_credential_library + for each row execute procedure immutable_columns('target_id', 'credential_library_id', 'credential_purpose', 'create_time'); + + -- target_library provides the store id along with the other data stored in + -- target_credential_library + create view target_library + as + select + tcl.target_id, + tcl.credential_library_id, + tcl.credential_purpose, + cl.store_id + from + target_credential_library tcl, + credential_library cl + where + cl.public_id = tcl.credential_library_id; + +commit; diff --git a/internal/db/schema/migrations/postgres/10/06_session.up.sql b/internal/db/schema/migrations/postgres/10/06_session.up.sql new file mode 100644 index 000000000..2592034df --- /dev/null +++ b/internal/db/schema/migrations/postgres/10/06_session.up.sql @@ -0,0 +1,57 @@ +begin; + + create table session_credential_dynamic ( + session_id wt_public_id not null + constraint session_fkey + references session (public_id) + on delete cascade + on update cascade, + library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + credential_id wt_public_id + constraint credential_dynamic_fkey + references credential_dynamic (public_id) + on delete cascade + on update cascade, + credential_purpose text not null + constraint credential_purpose_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + primary key(session_id, library_id, credential_purpose), + create_time wt_timestamp, + constraint session_credential_dynamic_credential_id_uq + unique(credential_id) + ); + comment on table session_credential_dynamic is + 'session_credential_dynamic is a join table between the session and dynamic credential tables. ' + 'It also contains the credential purpose the relationship represents.'; + + create trigger default_create_time_column before insert on session_credential_dynamic + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on session_credential_dynamic + for each row execute procedure immutable_columns('session_id', 'library_id', 'credential_purpose', 'create_time'); + + -- revoke_credentials revokes any active credentials for a session when the + -- session enters the canceling or terminated states. + create function revoke_credentials() + returns trigger + as $$ + begin + if new.state in ('canceling', 'terminated') then + update credential_vault_credential + set status = 'revoke' + where session_id = new.session_id + and status = 'active'; + end if; + return new; + end; + $$ language plpgsql; + create trigger revoke_credentials after insert on session_state + for each row execute procedure revoke_credentials(); + +commit; diff --git a/internal/db/schema/postgres_migration.gen.go b/internal/db/schema/postgres_migration.gen.go index 5c1779161..b575cfa0e 100644 --- a/internal/db/schema/postgres_migration.gen.go +++ b/internal/db/schema/postgres_migration.gen.go @@ -4,7 +4,7 @@ package schema func init() { migrationStates["postgres"] = migrationState{ - binarySchemaVersion: 10001, + binarySchemaVersion: 10006, upMigrations: map[int][]byte{ 1: []byte(` create domain wt_public_id as text @@ -4998,6 +4998,1049 @@ alter table job_run alter table job_run add constraint server_id_must_not_be_empty check(length(trim(server_id)) > 0); +`), + 10002: []byte(` +create function wt_is_sentinel(string text) + returns bool + as $$ + select length(trim(leading u&'\fffe ' from string)) > 0 AND starts_with(string, u&'\fffe'); + $$ language sql + immutable + returns null on null input; + comment on function wt_is_sentinel is + 'wt_is_sentinel returns true if string is a sentinel value'; + + create domain wt_sentinel as text + constraint wt_sentinel_not_valid + check( + wt_is_sentinel(value) + or + length(trim(u&'\fffe ' from value)) > 0 + ); + comment on domain wt_sentinel is + 'A non-empty string with a Unicode prefix of U+FFFE to indicate it is a sentinel value'; + + create function wt_to_sentinel(string text) + returns text + as $$ + select concat(u&'\fffe', trim(ltrim(string, u&'\fffe '))); + $$ language sql + immutable + returns null on null input; + comment on function wt_to_sentinel is + 'wt_to_sentinel takes string and returns it as a wt_sentinel'; +`), + 10003: []byte(` +-- credential_store + create table credential_store ( + public_id wt_public_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_fkey + references iam_scope (public_id) + on delete cascade + on update cascade, + + -- The order of columns is important for performance. See: + -- https://dba.stackexchange.com/questions/58970/enforcing-constraints-two-tables-away/58972#58972 + -- https://dba.stackexchange.com/questions/27481/is-a-composite-index-also-good-for-queries-on-the-first-field + constraint credential_store_scope_id_public_id_uq + unique(scope_id, public_id) + ); + comment on table credential_store is + 'credential_store is a base table for the credential store type. ' + 'Each row is owned by a single scope and maps 1-to-1 to a row in one of the credential store subtype tables.'; + + create trigger immutable_columns before update on credential_store + for each row execute procedure immutable_columns('public_id', 'scope_id'); + + -- insert_credential_store_subtype() is a before insert trigger + -- function for subtypes of credential_store + create function insert_credential_store_subtype() + returns trigger + as $$ + begin + insert into credential_store + (public_id, scope_id) + values + (new.public_id, new.scope_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_store_subtype() is an after delete trigger + -- function for subtypes of credential_store + create function delete_credential_store_subtype() + returns trigger + as $$ + begin + delete from credential_store + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + + -- credential_library + create table credential_library ( + public_id wt_public_id primary key, + store_id wt_public_id not null + constraint credential_store_fkey + references credential_store (public_id) + on delete cascade + on update cascade, + constraint credential_library_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_library is + 'credential_library is a base table for the credential library type and a child table of credential_store. ' + 'Each row maps 1-to-1 to a row in one of the credential library subtype tables.'; + + create trigger immutable_columns before update on credential_library + for each row execute procedure immutable_columns('public_id', 'store_id'); + + -- insert_credential_library_subtype() is a before insert trigger + -- function for subtypes of credential_library + create function insert_credential_library_subtype() + returns trigger + as $$ + begin + insert into credential_library + (public_id, store_id) + values + (new.public_id, new.store_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_library_subtype() is an after delete trigger + -- function for subtypes of credential_library + create function delete_credential_library_subtype() + returns trigger + as $$ + begin + delete from credential_library + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential + create table credential ( + public_id wt_public_id primary key + ); + comment on table credential is + 'credential is a base table for the credential type. '; + + create trigger immutable_columns before update on credential + for each row execute procedure immutable_columns('public_id'); + + -- insert_credential_subtype() is a before insert trigger + -- function for subtypes of credential + create function insert_credential_subtype() + returns trigger + as $$ + begin + insert into credential + (public_id) + values + (new.public_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_subtype() is an after delete trigger + -- function for subtypes of credential + create function delete_credential_subtype() + returns trigger + as $$ + begin + delete from credential + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential_static + create table credential_static ( + public_id wt_public_id primary key + constraint credential_fkey + references credential (public_id) + on delete cascade + on update cascade, + store_id wt_public_id not null + constraint credential_store_fkey + references credential_store (public_id) + on delete cascade + on update cascade, + constraint credential_static_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_static is + 'credential_static is a base table for the credential static type. ' + 'It is a credential subtype and a child table of credential_store. '; + + create trigger immutable_columns before update on credential_static + for each row execute procedure immutable_columns('public_id', 'store_id'); + + create trigger insert_credential_subtype before insert on credential_static + for each row execute procedure insert_credential_subtype(); + + create trigger delete_credential_subtype after delete on credential_static + for each row execute procedure delete_credential_subtype(); + + -- insert_credential_static_subtype() is a before insert trigger + -- function for subtypes of credential_static + create function insert_credential_static_subtype() + returns trigger + as $$ + begin + insert into credential_static + (public_id, store_id) + values + (new.public_id, new.store_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_static_subtype() is an after delete trigger + -- function for subtypes of credential_static + create function delete_credential_static_subtype() + returns trigger + as $$ + begin + delete from credential_static + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + -- credential_dynamic + create table credential_dynamic ( + public_id wt_public_id primary key + constraint credential_fkey + references credential (public_id) + on delete cascade + on update cascade, + library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + constraint credential_dynamic_library_id_public_id_uq + unique(library_id, public_id) + ); + comment on table credential_dynamic is + 'credential_dynamic is a base table for the credential dynamic type. ' + 'It is a credential subtype and a child table of credential_library. '; + + create trigger immutable_columns before update on credential_dynamic + for each row execute procedure immutable_columns('public_id', 'library_id'); + + create trigger insert_credential_subtype before insert on credential_dynamic + for each row execute procedure insert_credential_subtype(); + + create trigger delete_credential_subtype after delete on credential_dynamic + for each row execute procedure delete_credential_subtype(); + + -- insert_credential_dynamic_subtype() is a before insert trigger + -- function for subtypes of credential_dynamic + create function insert_credential_dynamic_subtype() + returns trigger + as $$ + begin + insert into credential_dynamic + (public_id, library_id) + values + (new.public_id, new.library_id); + return new; + end; + $$ language plpgsql; + + -- delete_credential_dynamic_subtype() is an after delete trigger + -- function for subtypes of credential_dynamic + create function delete_credential_dynamic_subtype() + returns trigger + as $$ + begin + delete from credential_dynamic + where public_id = old.public_id; + return null; -- result is ignored since this is an after trigger + end; + $$ language plpgsql; + + create table credential_purpose_enm ( + name text primary key + constraint only_predefined_credential_purposes_allowed + check ( + name in ( + 'application', + 'ingress', + 'egress' + ) + ) + ); + comment on table credential_purpose_enm is + 'credential_purpose_enm is an enumeration table for credential purposes. ' + 'It contains rows for representing the application, egress, and ingress credential purposes.'; + + insert into credential_purpose_enm (name) + values + ('application'), + ('ingress'), + ('egress'); +`), + 10004: []byte(` +create table credential_vault_store ( + public_id wt_public_id primary key, + scope_id wt_scope_id not null + constraint iam_scope_fkey + references iam_scope (public_id) + on delete cascade + on update cascade, + name wt_name, + description wt_description, + create_time wt_timestamp, + update_time wt_timestamp, + -- delete_time is set to indicate the row has been soft deleted + delete_time timestamp with time zone, + version wt_version, + vault_address wt_url not null, + -- the remaining text columns can be null but if they are not null, they + -- cannot contain an empty string + namespace text + constraint namespace_must_not_be_empty + check(length(trim(namespace)) > 0), + ca_cert bytea -- PEM encoded certificate bundle + constraint ca_cert_must_not_be_empty + check(length(ca_cert) > 0), + tls_server_name text + constraint tls_server_name_must_not_be_empty + check(length(trim(tls_server_name)) > 0), + tls_skip_verify boolean default false not null, + constraint credential_store_fkey + foreign key (scope_id, public_id) + references credential_store (scope_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_store_scope_id_name_uq + unique(scope_id, name) + ); + comment on table credential_vault_store is + 'credential_vault_store is a table where each row is a resource that represents a vault credential store. ' + 'It is a credential_store subtype.'; + + create trigger update_version_column after update on credential_vault_store + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_store + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_store + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_store + for each row execute procedure immutable_columns('public_id', 'scope_id','create_time'); + + create trigger insert_credential_store_subtype before insert on credential_vault_store + for each row execute procedure insert_credential_store_subtype(); + + create trigger delete_credential_store_subtype after delete on credential_vault_store + for each row execute procedure delete_credential_store_subtype(); + + -- before_soft_delete_credential_vault_store is a before update trigger for + -- the credential_vault_store table that makes the delete_time column a + -- set once column. Once the delete_time column is set to a value other than + -- null, it cannot be changed. If the current delete_time of a row is not null + -- and an update contains a value for delete_time different from the current + -- value, this trigger will raise an error with error code 23602 which is a + -- class 23 integrity constraint violation: set_once_violation. + create function before_soft_delete_credential_vault_store() + returns trigger + as $$ + begin + if new.delete_time is distinct from old.delete_time then + if old.delete_time is not null then + raise exception 'set_once_violation: %.%', tg_table_name, 'delete_time' using + errcode = '23602', + schema = tg_table_schema, + table = tg_table_name, + column = 'delete_time'; + end if; + end if; + return new; + end; + $$ language plpgsql; + + create trigger before_soft_delete_credential_vault_store before update on credential_vault_store + for each row execute procedure before_soft_delete_credential_vault_store(); + + -- after_soft_delete_credential_vault_store is an after update trigger for the + -- credential_vault_store table that performs cleanup actions when a + -- credential store is soft deleted. A credential store is considered "soft + -- deleted" if the delete_time for the row is updated from null to not null. + -- + -- When a credential store is soft deleted, this trigger: + -- * marks any active Vault tokens owned by the credential store for revocation + -- * deletes any credential library owned by the credential store + create function after_soft_delete_credential_vault_store() + returns trigger + as $$ + begin + if new.delete_time is distinct from old.delete_time then + if old.delete_time is null then + + -- mark current and maintaining tokens as revoke + update credential_vault_token + set status = 'revoke' + where store_id = new.public_id + and status in ('current', 'maintaining'); + + -- delete the store's libraries + delete + from credential_vault_library + where store_id = new.public_id; + + end if; + end if; + return null; + end; + $$ language plpgsql; + + create trigger after_soft_delete_credential_vault_store after update on credential_vault_store + for each row execute procedure after_soft_delete_credential_vault_store(); + + create table credential_vault_token_status_enm ( + name text primary key + constraint only_predefined_token_statuses_allowed + check ( + name in ( + 'current', + 'maintaining', + 'revoke', + 'revoked', + 'expired' + ) + ) + ); + comment on table credential_vault_token_status_enm is + 'credential_vault_token_status_enm is an enumeration table for the status of vault tokens. ' + 'It contains rows for representing the current, maintaining, revoke, revoked, and expired statuses.'; + + insert into credential_vault_token_status_enm (name) + values + ('current'), + ('maintaining'), + ('revoke'), + ('revoked'), + ('expired'); + + create table credential_vault_token ( + token_hmac bytea primary key, -- hmac-sha256(token, key(blake2b-256(token_accessor)) + token bytea not null, -- encrypted value + store_id wt_public_id not null + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + create_time wt_timestamp, + update_time wt_timestamp, + last_renewal_time timestamp with time zone not null, + expiration_time timestamp with time zone not null + constraint last_renewal_time_must_be_before_expiration_time + check(last_renewal_time < expiration_time), + key_id text not null + constraint kms_database_key_version_fkey + references kms_database_key_version (private_id) + on delete restrict + on update cascade, + status text not null + constraint credential_vault_token_status_enm_fkey + references credential_vault_token_status_enm (name) + on delete restrict + on update cascade + ); + comment on table credential_vault_token is + 'credential_vault_token is a table where each row contains a Vault token for one Vault credential store. ' + 'A credential store can have only one vault token with the status of current'; + comment on column credential_vault_token.token_hmac is + 'token_hmac contains the hmac-sha256 value of the token. ' + 'The hmac key is the blake2b-256 value of the token accessor.'; + + -- https://www.postgresql.org/docs/current/indexes-partial.html + create unique index credential_vault_token_current_status_constraint + on credential_vault_token (store_id) + where status = 'current'; + + create index credential_vault_token_expiration_time_ix + on credential_vault_token(expiration_time); + comment on index credential_vault_token_expiration_time_ix is + 'the credential_vault_token_expiration_time_ix is used by the token renewal job'; + + create trigger update_time_column before update on credential_vault_token + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_token + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_token + for each row execute procedure immutable_columns('token_hmac', 'token', 'store_id','create_time'); + + -- insert_credential_vault_token() is a before insert trigger + -- function for credential_vault_token that changes the status of the current + -- token to 'maintaining' + create function insert_credential_vault_token() + returns trigger + as $$ + begin + update credential_vault_token + set status = 'maintaining' + where store_id = new.store_id + and status = 'current'; + return new; + end; + $$ language plpgsql; + + create trigger insert_credential_vault_token before insert on credential_vault_token + for each row execute procedure insert_credential_vault_token(); + + create table credential_vault_client_certificate ( + store_id wt_public_id primary key + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + certificate bytea not null -- PEM encoded certificate + constraint certificate_must_not_be_empty + check(length(certificate) > 0), + certificate_key bytea not null -- encrypted PEM encoded private key for certificate + constraint certificate_key_must_not_be_empty + check(length(certificate_key) > 0), + certificate_key_hmac bytea not null + constraint certificate_key_hmac_must_not_be_empty + check(length(certificate_key_hmac) > 0), + key_id text not null + constraint kms_database_key_version_fkey + references kms_database_key_version (private_id) + on delete restrict + on update cascade + ); + comment on table credential_vault_client_certificate is + 'credential_vault_client_certificate is a table where each row contains a client certificate that a credential_vault_store uses for mTLS when connecting to Vault. ' + 'A credential_vault_store can have 0 or 1 client certificates.'; + + create trigger immutable_columns before update on credential_vault_client_certificate + for each row execute procedure immutable_columns('store_id'); + + create table credential_vault_http_method_enm ( + name text primary key + constraint only_predefined_http_methods_allowed + check ( + name in ( + 'GET', + 'POST' + ) + ) + ); + comment on table credential_vault_http_method_enm is + 'credential_vault_http_method_enm is an enumeration table for the http method used by a vault library when communicating with vault. ' + 'It contains rows for representing the HTTP GET and the HTTP POST methods.'; + + insert into credential_vault_http_method_enm (name) + values + ('GET'), + ('POST'); + + create table credential_vault_library ( + public_id wt_public_id primary key, + store_id wt_public_id not null + constraint credential_vault_store_fkey + references credential_vault_store (public_id) + on delete cascade + on update cascade, + name wt_name, + description wt_description, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + vault_path text not null + constraint vault_path_must_not_be_empty + check(length(trim(vault_path)) > 0), + http_method text not null + constraint credential_vault_http_method_enm_fkey + references credential_vault_http_method_enm (name) + on delete restrict + on update cascade, + http_request_body bytea + constraint http_request_body_only_allowed_with_post_method + check( + http_request_body is null + or + ( + http_method = 'POST' + and + length(http_request_body) > 0 + ) + ), + constraint credential_vault_library_store_id_name_uq + unique(store_id, name), + constraint credential_library_fkey + foreign key (store_id, public_id) + references credential_library (store_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_library_store_id_public_id_uq + unique(store_id, public_id) + ); + comment on table credential_vault_library is + 'credential_vault_library is a table where each row is a resource that represents a vault credential library. ' + 'It is a credential_library subtype and a child table of credential_vault_store.'; + + create trigger update_version_column after update on credential_vault_library + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_library + for each row execute procedure update_time_column(); + + create trigger default_create_time_column before insert on credential_vault_library + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_library + for each row execute procedure immutable_columns('public_id', 'store_id','create_time'); + + create trigger insert_credential_library_subtype before insert on credential_vault_library + for each row execute procedure insert_credential_library_subtype(); + + create trigger delete_credential_library_subtype after delete on credential_vault_library + for each row execute procedure delete_credential_library_subtype(); + + + -- before_insert_credential_vault_library is a before insert trigger for + -- the credential_vault_library table that prevents a library from being + -- inserted for a soft deleted credential store. + create function before_insert_credential_vault_library() + returns trigger + as $$ + declare + delete_time_val timestamp with time zone; + begin + select delete_time into delete_time_val + from credential_vault_store + where public_id = new.store_id; + + if delete_time_val is not null then + raise exception 'foreign_key_violation: %.%', tg_table_name, 'store_id' using + errcode = '23503', + schema = tg_table_schema, + table = tg_table_name, + column = 'store_id'; + end if; + return new; + end; + $$ language plpgsql; + + create trigger before_insert_credential_vault_library before insert on credential_vault_library + for each row execute procedure before_insert_credential_vault_library(); + + create table credential_vault_credential_status_enm ( + name text primary key + constraint only_predefined_credential_statuses_allowed + check ( + name in ( + 'active', + 'revoke', + 'revoked', + 'expired', + 'unknown' + ) + ) + ); + comment on table credential_vault_credential_status_enm is + 'credential_vault_credential_status_enm is an enumeration table for the status of vault credentials. ' + 'It contains rows for representing the active, revoke, revoked, expired, and unknown statuses.'; + + insert into credential_vault_credential_status_enm (name) + values + ('active'), + ('revoke'), + ('revoked'), + ('expired'), + ('unknown'); + + create table credential_vault_credential ( + public_id wt_public_id primary key, + library_id wt_public_id + constraint credential_vault_library_fkey + references credential_vault_library (public_id) + on delete set null + on update cascade, + session_id wt_public_id + constraint session_fkey + references session (public_id) + on delete set null + on update cascade, + token_hmac bytea not null + constraint credential_vault_token_fkey + references credential_vault_token (token_hmac) + on delete cascade + on update cascade, + create_time wt_timestamp, + update_time wt_timestamp, + version wt_version, + external_id wt_sentinel not null, + last_renewal_time timestamp with time zone not null, + expiration_time timestamp with time zone not null + constraint last_renewal_time_must_be_before_expiration_time + check(last_renewal_time < expiration_time), + is_renewable boolean not null, + status text not null + constraint credential_vault_credential_status_enm_fkey + references credential_vault_credential_status_enm (name) + on delete restrict + on update cascade, + constraint credential_dynamic_fkey + foreign key (library_id, public_id) + references credential_dynamic (library_id, public_id) + on delete cascade + on update cascade, + constraint credential_vault_credential_library_id_public_id_uq + unique(library_id, public_id) + ); + comment on table credential_vault_credential is + 'credential_vault_credential is a table where each row contains the lease information for a single Vault secret retrieved from a vault credential library for a session.'; + + create trigger update_version_column after update on credential_vault_credential + for each row execute procedure update_version_column(); + + create trigger update_time_column before update on credential_vault_credential + for each row execute procedure update_time_column(); + + -- update_credential_status_column() is a before update trigger function for + -- credential_vault_credential that changes the status of the credential to 'revoke' if + -- the session_id is updated to null + create function update_credential_status_column() + returns trigger + as $$ + begin + if new.session_id is distinct from old.session_id then + if new.session_id is null and old.status = 'active' then + new.status = 'revoke'; + end if; + end if; + return new; + end; + $$ language plpgsql; + + create trigger update_credential_status_column before update on credential_vault_credential + for each row execute procedure update_credential_status_column(); + + -- not_null_columns() will make the column names not null which are passed as + -- parameters when the trigger is created. It raises error code 23502 which is a + -- class 23 integrity constraint violation: not null column + create function not_null_columns() + returns trigger + as $$ + declare + col_name text; + new_value text; + begin + foreach col_name in array tg_argv loop + execute format('SELECT $1.%I', col_name) into new_value using new; + if new_value is null then + raise exception 'not null column: %.%', tg_table_name, col_name using + errcode = '23502', + schema = tg_table_schema, + table = tg_table_name, + column = col_name; + end if; + end loop; + return new; + end; + $$ language plpgsql; + + comment on function not_null_columns() is + 'function used in before insert triggers to make columns not null on insert, but are allowed be updated to null'; + + create trigger not_null_columns before insert on credential_vault_credential + for each row execute procedure not_null_columns('library_id', 'session_id'); + + create trigger default_create_time_column before insert on credential_vault_credential + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on credential_vault_credential + for each row execute procedure immutable_columns('external_id', 'create_time'); + + create trigger insert_credential_dynamic_subtype before insert on credential_vault_credential + for each row execute procedure insert_credential_dynamic_subtype(); + + create trigger delete_credential_dynamic_subtype after delete on credential_vault_credential + for each row execute procedure delete_credential_dynamic_subtype(); + + create index credential_vault_credential_expiration_time_ix + on credential_vault_credential(expiration_time); + comment on index credential_vault_credential_expiration_time_ix is + 'the credential_vault_credential_expiration_time_ix is used by the credential renewal job'; + + insert into oplog_ticket (name, version) + values + ('credential_vault_store', 1), + ('credential_vault_library', 1), + ('credential_vault_credential', 1) ; + + create view credential_vault_store_private as + with + active_tokens as ( + select token_hmac, + token, -- encrypted + store_id, + create_time, + update_time, + last_renewal_time, + expiration_time, + -- renewal time is the midpoint between the last renewal time and the expiration time + last_renewal_time + (expiration_time - last_renewal_time) / 2 as renewal_time, + key_id, + status + from credential_vault_token + where status in ('current', 'maintaining', 'revoke') + ) + select store.public_id as public_id, + store.scope_id as scope_id, + store.name as name, + store.description as description, + store.create_time as create_time, + store.update_time as update_time, + store.delete_time as delete_time, + store.version as version, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.public_id as store_id, + token.token_hmac as token_hmac, + token.token as ct_token, -- encrypted + token.create_time as token_create_time, + token.update_time as token_update_time, + token.last_renewal_time as token_last_renewal_time, + token.expiration_time as token_expiration_time, + token.renewal_time as token_renewal_time, + token.key_id as token_key_id, + token.status as token_status, + cert.certificate as client_cert, + cert.certificate_key as ct_client_key, -- encrypted + cert.certificate_key_hmac as client_cert_key_hmac, + cert.key_id as client_key_id + from credential_vault_store store + left join active_tokens token + on store.public_id = token.store_id + left join credential_vault_client_certificate cert + on store.public_id = cert.store_id; + comment on view credential_vault_store_private is + 'credential_vault_store_private is a view where each row contains a credential store and the credential store''s data needed to connect to Vault. ' + 'The view returns a separate row for each current, maintaining and revoke token; maintaining tokens should only be used for token/credential renewal and revocation. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_store_public as + select public_id, + scope_id, + name, + description, + create_time, + update_time, + version, + vault_address, + namespace, + ca_cert, + tls_server_name, + tls_skip_verify, + token_hmac, + token_create_time, + token_update_time, + token_last_renewal_time, + token_expiration_time, + client_cert, + client_cert_key_hmac + from credential_vault_store_private + where token_status = 'current' + and delete_time is null; + comment on view credential_vault_store_public is + 'credential_vault_store_public is a view where each row contains a credential store. ' + 'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_library_private as + select library.public_id as public_id, + library.store_id as store_id, + library.name as name, + library.description as description, + library.create_time as create_time, + library.update_time as update_time, + library.version as version, + library.vault_path as vault_path, + library.http_method as http_method, + library.http_request_body as http_request_body, + store.scope_id as scope_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + store.token_hmac as token_hmac, + store.ct_token as ct_token, -- encrypted + store.token_key_id as token_key_id, + store.client_cert as client_cert, + store.ct_client_key as ct_client_key, -- encrypted + store.client_key_id as client_key_id + from credential_vault_library library + join credential_vault_store_private store + on library.store_id = store.public_id + and store.token_status = 'current'; + comment on view credential_vault_library_private is + 'credential_vault_library_private is a view where each row contains a credential library and the credential library''s data needed to connect to Vault. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; + + create view credential_vault_credential_private as + select credential.public_id as public_id, + credential.library_id as library_id, + credential.session_id as session_id, + credential.create_time as create_time, + credential.update_time as update_time, + credential.version as version, + credential.external_id as external_id, + credential.last_renewal_time as last_renewal_time, + credential.expiration_time as expiration_time, + credential.is_renewable as is_renewable, + credential.status as status, + credential.last_renewal_time + (credential.expiration_time - credential.last_renewal_time) / 2 as renewal_time, + token.token_hmac as token_hmac, + token.token as ct_token, -- encrypted + token.create_time as token_create_time, + token.update_time as token_update_time, + token.last_renewal_time as token_last_renewal_time, + token.expiration_time as token_expiration_time, + token.key_id as token_key_id, + token.status as token_status, + store.scope_id as scope_id, + store.vault_address as vault_address, + store.namespace as namespace, + store.ca_cert as ca_cert, + store.tls_server_name as tls_server_name, + store.tls_skip_verify as tls_skip_verify, + cert.certificate as client_cert, + cert.certificate_key as ct_client_key, -- encrypted + cert.certificate_key_hmac as client_cert_key_hmac, + cert.key_id as client_key_id + from credential_vault_credential credential + join credential_vault_token token + on credential.token_hmac = token.token_hmac + join credential_vault_store store + on token.store_id = store.public_id + left join credential_vault_client_certificate cert + on store.public_id = cert.store_id + where credential.expiration_time != 'infinity'::date; + comment on view credential_vault_credential_private is + 'credential_vault_credential_private is a view where each row contains a credential, ' + 'the vault token used to issue the credential, and the credential store data needed to connect to Vault. ' + 'Each row may contain encrypted data. This view should not be used to retrieve data which will be returned external to boundary.'; +`), + 10005: []byte(` +create table target_credential_library ( + target_id wt_public_id not null + constraint target_fkey + references target (public_id) + on delete cascade + on update cascade, + credential_library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + credential_purpose text not null + constraint credential_purpose_enm_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + create_time wt_timestamp, + primary key(target_id, credential_library_id, credential_purpose) + ); + comment on table target_credential_library is + 'target_credential_library is a join table between the target and credential_library tables. ' + 'It also contains the credential purpose the relationship represents.'; + + create trigger default_create_time_column before insert on target_credential_library + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on target_credential_library + for each row execute procedure immutable_columns('target_id', 'credential_library_id', 'credential_purpose', 'create_time'); + + -- target_library provides the store id along with the other data stored in + -- target_credential_library + create view target_library + as + select + tcl.target_id, + tcl.credential_library_id, + tcl.credential_purpose, + cl.store_id + from + target_credential_library tcl, + credential_library cl + where + cl.public_id = tcl.credential_library_id; +`), + 10006: []byte(` +create table session_credential_dynamic ( + session_id wt_public_id not null + constraint session_fkey + references session (public_id) + on delete cascade + on update cascade, + library_id wt_public_id not null + constraint credential_library_fkey + references credential_library (public_id) + on delete cascade + on update cascade, + credential_id wt_public_id + constraint credential_dynamic_fkey + references credential_dynamic (public_id) + on delete cascade + on update cascade, + credential_purpose text not null + constraint credential_purpose_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + primary key(session_id, library_id, credential_purpose), + create_time wt_timestamp, + constraint session_credential_dynamic_credential_id_uq + unique(credential_id) + ); + comment on table session_credential_dynamic is + 'session_credential_dynamic is a join table between the session and dynamic credential tables. ' + 'It also contains the credential purpose the relationship represents.'; + + create trigger default_create_time_column before insert on session_credential_dynamic + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on session_credential_dynamic + for each row execute procedure immutable_columns('session_id', 'library_id', 'credential_purpose', 'create_time'); + + -- revoke_credentials revokes any active credentials for a session when the + -- session enters the canceling or terminated states. + create function revoke_credentials() + returns trigger + as $$ + begin + if new.state in ('canceling', 'terminated') then + update credential_vault_credential + set status = 'revoke' + where session_id = new.session_id + and status = 'active'; + end if; + return new; + end; + $$ language plpgsql; + create trigger revoke_credentials after insert on session_state + for each row execute procedure revoke_credentials(); `), 2001: []byte(` -- log_migration entries represent logs generated during migrations diff --git a/internal/errors/code.go b/internal/errors/code.go index b33fbc679..551f10ccb 100644 --- a/internal/errors/code.go +++ b/internal/errors/code.go @@ -37,6 +37,8 @@ const ( TooShort Code = 113 // TooShort represents an error that means the provided input is not meeting minimum length requirements AccountAlreadyAssociated Code = 114 // AccountAlreadyAssociated represents an attempt to associate an account failed since it was already associated. InvalidJobRunState Code = 115 // InvalidJobRunState represents that a JobRun was in an invalid state + InvalidDynamicCredential Code = 116 // InvalidDynamicCredential represents that a dynamic credential for a session was in an invalid state + JobAlreadyRunning Code = 117 // JobAlreadyRunning represents that a Job is already running when an attempt to run again was made AuthAttemptExpired Code = 198 // AuthAttemptExpired represents an expired authentication attempt AuthMethodInactive Code = 199 // AuthMethodInactive represents an error that means the auth method is not active. @@ -92,6 +94,13 @@ const ( // External system errors are reserved codes 3000-3999 Unavailable Code = 3000 // Unavailable represents that an external system is unavailable + // Vault specific errors + VaultTokenNotOrphan Code = 3010 // VaultTokenNotOrphan represents an error for a Vault token that is not an orphan token + VaultTokenNotPeriodic Code = 3011 // VaultTokenNotPeriodic represents an error for a Vault token that is not a periodic token + VaultTokenNotRenewable Code = 3012 // VaultTokenNotRenewable represents an error for a Vault token that is not renewable + VaultTokenMissingCapabilities Code = 3013 // VaultTokenMissingCapabilities represents an error for a Vault token that is missing capabilities + VaultCredentialRequest Code = 3014 // VaultCredentialRequest represents an error returned from Vault when retrieving a credential + // OIDC authentication provided errors OidcProviderCallbackError Code = 4000 // OidcProviderCallbackError represents an error that is passed by the OIDC provider to the callback endpoint ) diff --git a/internal/errors/code_test.go b/internal/errors/code_test.go index 3198f0a48..7c32d75cb 100644 --- a/internal/errors/code_test.go +++ b/internal/errors/code_test.go @@ -107,6 +107,16 @@ func TestCode_Both_String_Info(t *testing.T) { c: InvalidJobRunState, want: InvalidJobRunState, }, + { + name: "JobAlreadyRunning", + c: JobAlreadyRunning, + want: JobAlreadyRunning, + }, + { + name: "InvalidDynamicCredential", + c: InvalidDynamicCredential, + want: InvalidDynamicCredential, + }, { name: "InternalError", c: Internal, @@ -247,6 +257,26 @@ func TestCode_Both_String_Info(t *testing.T) { c: Unavailable, want: Unavailable, }, + { + name: "VaultTokenNotOrphan", + c: VaultTokenNotOrphan, + want: VaultTokenNotOrphan, + }, + { + name: "VaultTokenNotPeriodic", + c: VaultTokenNotPeriodic, + want: VaultTokenNotPeriodic, + }, + { + name: "VaultTokenNotRenewable", + c: VaultTokenNotRenewable, + want: VaultTokenNotRenewable, + }, + { + name: "VaultCredentialRequest", + c: VaultCredentialRequest, + want: VaultCredentialRequest, + }, { name: "OidcProviderCallbackError", c: OidcProviderCallbackError, diff --git a/internal/errors/info.go b/internal/errors/info.go index e96f141e9..f70867dfc 100644 --- a/internal/errors/info.go +++ b/internal/errors/info.go @@ -88,6 +88,14 @@ var errorCodeInfo = map[Code]Info{ Message: "job run is already in a final run state", Kind: Integrity, }, + JobAlreadyRunning: { + Message: "job already running", + Kind: State, + }, + InvalidDynamicCredential: { + Message: "dynamic credential for session is in an invalid state", + Kind: Integrity, + }, PasswordTooShort: { Message: "too short", Kind: Password, @@ -192,6 +200,26 @@ var errorCodeInfo = map[Code]Info{ Message: "external system unavailable", Kind: External, }, + VaultTokenNotOrphan: { + Message: "vault token is not an orphan token", + Kind: VaultToken, + }, + VaultTokenNotPeriodic: { + Message: "vault token is not a periodic token", + Kind: VaultToken, + }, + VaultTokenNotRenewable: { + Message: "vault token is not renewable", + Kind: VaultToken, + }, + VaultTokenMissingCapabilities: { + Message: "vault token is missing capabilities", + Kind: VaultToken, + }, + VaultCredentialRequest: { + Message: "request for a new credential from vault failed", + Kind: External, + }, OidcProviderCallbackError: { Message: "oidc provider callback error", Kind: External, diff --git a/internal/errors/kind.go b/internal/errors/kind.go index ca8d5c482..6f4692464 100644 --- a/internal/errors/kind.go +++ b/internal/errors/kind.go @@ -14,6 +14,7 @@ const ( Encoding State External + VaultToken ) func (e Kind) String() string { @@ -28,5 +29,6 @@ func (e Kind) String() string { Encoding: "encoding issue", State: "state violation", External: "external system issue", + VaultToken: "vault token issue", }[e] } diff --git a/internal/errors/kind_test.go b/internal/errors/kind_test.go index fb9b826e1..6b484967f 100644 --- a/internal/errors/kind_test.go +++ b/internal/errors/kind_test.go @@ -38,6 +38,11 @@ func TestKind_String(t *testing.T) { e: External, want: "external system issue", }, + { + name: "VaultToken", + e: VaultToken, + want: "vault token issue", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 231bdbda2..c56bd2704 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -17,6 +17,12 @@ { "name": "AuthTokenService" }, + { + "name": "CredentialLibraryService" + }, + { + "name": "CredentialStoreService" + }, { "name": "GroupService" }, @@ -596,6 +602,294 @@ ] } }, + "/v1/credential-libraries": { + "get": { + "summary": "Lists all Credential Library.", + "operationId": "CredentialLibraryService_ListCredentialLibraries", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.ListCredentialLibrariesResponse" + } + } + }, + "parameters": [ + { + "name": "credential_store_id", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "filter", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialLibraryService" + ] + }, + "post": { + "summary": "Creates a single Credential Library.", + "operationId": "CredentialLibraryService_CreateCredentialLibrary", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + ], + "tags": [ + "controller.api.services.v1.CredentialLibraryService" + ] + } + }, + "/v1/credential-libraries/{id}": { + "get": { + "summary": "Gets a single Credential Library.", + "operationId": "CredentialLibraryService_GetCredentialLibrary", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialLibraryService" + ] + }, + "delete": { + "summary": "Deletes a Credential Library", + "operationId": "CredentialLibraryService_DeleteCredentialLibrary", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.DeleteCredentialLibraryResponse" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialLibraryService" + ] + }, + "patch": { + "summary": "Updates a Credential Library.", + "operationId": "CredentialLibraryService_UpdateCredentialLibrary", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + }, + { + "name": "update_mask", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialLibraryService" + ] + } + }, + "/v1/credential-stores": { + "get": { + "summary": "Lists all Credential Stores.", + "operationId": "CredentialStoreService_ListCredentialStores", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.ListCredentialStoresResponse" + } + } + }, + "parameters": [ + { + "name": "scope_id", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "recursive", + "in": "query", + "required": false, + "type": "boolean" + }, + { + "name": "filter", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialStoreService" + ] + }, + "post": { + "summary": "Creates a single Credential Store.", + "operationId": "CredentialStoreService_CreateCredentialStore", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + ], + "tags": [ + "controller.api.services.v1.CredentialStoreService" + ] + } + }, + "/v1/credential-stores/{id}": { + "get": { + "summary": "Gets a single Credential Store.", + "operationId": "CredentialStoreService_GetCredentialStore", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialStoreService" + ] + }, + "delete": { + "summary": "Deletes a CredentialStore", + "operationId": "CredentialStoreService_DeleteCredentialStore", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.DeleteCredentialStoreResponse" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialStoreService" + ] + }, + "patch": { + "summary": "Updates a Credential Store.", + "operationId": "CredentialStoreService_UpdateCredentialStore", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + }, + { + "name": "update_mask", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialStoreService" + ] + } + }, "/v1/groups": { "get": { "summary": "Lists all Groups.", @@ -2257,6 +2551,39 @@ ] } }, + "/v1/targets/{id}:add-credential-libraries": { + "post": { + "summary": "Adds existing Credential Libraries to a Target.", + "operationId": "TargetService_AddTargetCredentialLibraries", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.services.v1.AddTargetCredentialLibrariesRequest" + } + } + ], + "tags": [ + "controller.api.services.v1.TargetService" + ] + } + }, "/v1/targets/{id}:add-host-sets": { "post": { "summary": "Adds existing Host Sets to a Target.", @@ -2324,6 +2651,39 @@ ] } }, + "/v1/targets/{id}:remove-credential-libraries": { + "post": { + "summary": "Removes Credential Libraries from the Target.", + "operationId": "TargetService_RemoveTargetCredentialLibraries", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.services.v1.RemoveTargetCredentialLibrariesRequest" + } + } + ], + "tags": [ + "controller.api.services.v1.TargetService" + ] + } + }, "/v1/targets/{id}:remove-host-sets": { "post": { "summary": "Removes Host Sets from the Target.", @@ -2357,6 +2717,39 @@ ] } }, + "/v1/targets/{id}:set-credential-libraries": { + "post": { + "summary": "Sets the Credential Libraries on the Target.", + "operationId": "TargetService_SetTargetCredentialLibraries", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.services.v1.SetTargetCredentialLibrariesRequest" + } + } + ], + "tags": [ + "controller.api.services.v1.TargetService" + ] + } + }, "/v1/targets/{id}:set-host-sets": { "post": { "summary": "Sets the Host Sets on the Target.", @@ -2856,6 +3249,139 @@ }, "title": "AuthToken contains all fields related to an Auth Token resource" }, + "controller.api.resources.credentiallibraries.v1.CredentialLibrary": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Output only. The ID of the Credential Library.", + "readOnly": true + }, + "credential_store_id": { + "type": "string", + "description": "The ID of the Credential Store of which this Credential Library is a part." + }, + "scope": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.ScopeInfo", + "description": "Output only. Scope information for this Credential Library.", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Optional name for identification purposes." + }, + "description": { + "type": "string", + "description": "Optional user-set description for identification purposes." + }, + "created_time": { + "type": "string", + "format": "date-time", + "description": "Output only. The time this resource was created.", + "readOnly": true + }, + "updated_time": { + "type": "string", + "format": "date-time", + "description": "Output only. The time this resource was last updated.", + "readOnly": true + }, + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used in mutation requests, after the initial creation, to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "type": { + "type": "string", + "description": "The Credential Library type." + }, + "attributes": { + "type": "object", + "description": "The attributes that are applicable for the specific Credential Library type." + }, + "authorized_actions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The available actions on this resource for this user.", + "readOnly": true + } + }, + "title": "CredentialLibrary contains all fields related to an Credential Library resource" + }, + "controller.api.resources.credentialstores.v1.CredentialStore": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Output only. The ID of the Credential Store.", + "readOnly": true + }, + "scope_id": { + "type": "string", + "description": "The ID of the Scope of which this Credential Store is a part." + }, + "scope": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.ScopeInfo", + "description": "Output only. Scope information for this Credential Store.", + "readOnly": true + }, + "name": { + "type": "string", + "description": "Optional name for identification purposes." + }, + "description": { + "type": "string", + "description": "Optional user-set description for identification purposes." + }, + "created_time": { + "type": "string", + "format": "date-time", + "description": "Output only. The time this resource was created.", + "readOnly": true + }, + "updated_time": { + "type": "string", + "format": "date-time", + "description": "Output only. The time this resource was last updated.", + "readOnly": true + }, + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used in mutation requests, after the initial creation, to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "type": { + "type": "string", + "description": "The Credential Store type." + }, + "attributes": { + "type": "object", + "description": "The attributes that are applicable for the specific Credential Store type." + }, + "authorized_actions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The available actions on this resource for this user.", + "readOnly": true + }, + "authorized_collection_actions": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "object" + } + }, + "description": "Output only. The authorized actions for the scope's collections.", + "readOnly": true + } + }, + "title": "CredentialStore contains all fields related to an Credential Store resource" + }, "controller.api.resources.groups.v1.Group": { "type": "object", "properties": { @@ -3622,6 +4148,39 @@ } } }, + "controller.api.resources.targets.v1.CredentialLibrary": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The ID of the Credential Library." + }, + "name": { + "type": "string", + "description": "Output only. The name of the Credential Library.", + "readOnly": true + }, + "description": { + "type": "string", + "description": "Output only. The description of the Credential Library.", + "readOnly": true + }, + "credential_store_id": { + "type": "string", + "description": "Output only. The Credential Store to which this Credential Library belongs.", + "readOnly": true + }, + "purpose": { + "type": "string", + "description": "The library's purpose for this target." + }, + "type": { + "type": "string", + "description": "Output only. The type of the credential library.", + "readOnly": true + } + } + }, "controller.api.resources.targets.v1.HostSet": { "type": "object", "properties": { @@ -3690,10 +4249,34 @@ "type": "string", "description": "Output only. The endpoint address that the worker will connect to, useful for setting TLS parameters.", "readOnly": true + }, + "credentials": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.targets.v1.SessionCredential" + }, + "description": "Output only. The credentials for this session.", + "readOnly": true } }, "description": "SessionAuthorization contains all fields related to authorization for a Session. It's in the Targets package because it's returned by a Target's authorize action." }, + "controller.api.resources.targets.v1.SessionCredential": { + "type": "object", + "properties": { + "credential_library": { + "$ref": "#/definitions/controller.api.resources.targets.v1.CredentialLibrary", + "description": "Output only. The library which generated this credential.", + "readOnly": true + }, + "secret": { + "type": "string", + "description": "Output only. The secret of this credential base64 encoded.", + "readOnly": true + } + }, + "description": "A credential for a session." + }, "controller.api.resources.targets.v1.Target": { "type": "object", "properties": { @@ -3769,6 +4352,21 @@ "type": "string", "description": "Optional boolean expression to filter the workers that are allowed to satisfy this request." }, + "credential_library_ids": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The IDs of the credential libraries associated with this Target." + }, + "credential_libraries": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.targets.v1.CredentialLibrary" + }, + "description": "Output only. The credential libraries associated with this Target.", + "readOnly": true + }, "attributes": { "type": "object", "description": "The attributes that are applicable for the specific Target." @@ -3997,6 +4595,40 @@ } } }, + "controller.api.services.v1.AddTargetCredentialLibrariesRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "credential_library_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "credential_libraries": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.services.v1.CredentialLibrary" + } + } + }, + "description": "The ids from both credential_library_ids and credential_libraries are used.\nWhen using credential_library_ids the default value \"application\" is used\nfor the purpose." + }, + "controller.api.services.v1.AddTargetCredentialLibrariesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, "controller.api.services.v1.AddTargetHostSetsRequest": { "type": "object", "properties": { @@ -4244,6 +4876,28 @@ } } }, + "controller.api.services.v1.CreateCredentialLibraryResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "item": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "controller.api.services.v1.CreateCredentialStoreResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "item": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, "controller.api.services.v1.CreateGroupResponse": { "type": "object", "properties": { @@ -4343,6 +4997,19 @@ } } }, + "controller.api.services.v1.CredentialLibrary": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "The id of the credential library." + }, + "purpose": { + "type": "string", + "description": "The purpose of the credential library for this target. Currently this can\nonly be \"application\" or left empty (which defaults the value to\n\"application\")." + } + } + }, "controller.api.services.v1.DeleteAccountResponse": { "type": "object" }, @@ -4352,6 +5019,12 @@ "controller.api.services.v1.DeleteAuthTokenResponse": { "type": "object" }, + "controller.api.services.v1.DeleteCredentialLibraryResponse": { + "type": "object" + }, + "controller.api.services.v1.DeleteCredentialStoreResponse": { + "type": "object" + }, "controller.api.services.v1.DeleteGroupResponse": { "type": "object" }, @@ -4403,6 +5076,22 @@ } } }, + "controller.api.services.v1.GetCredentialLibraryResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "controller.api.services.v1.GetCredentialStoreResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, "controller.api.services.v1.GetGroupResponse": { "type": "object", "properties": { @@ -4516,6 +5205,28 @@ } } }, + "controller.api.services.v1.ListCredentialLibrariesResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + } + }, + "controller.api.services.v1.ListCredentialStoresResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + } + }, "controller.api.services.v1.ListGroupsResponse": { "type": "object", "properties": { @@ -4735,6 +5446,33 @@ } } }, + "controller.api.services.v1.RemoveTargetCredentialLibrariesRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "credential_library_ids": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "controller.api.services.v1.RemoveTargetCredentialLibrariesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, "controller.api.services.v1.RemoveTargetHostSetsRequest": { "type": "object", "properties": { @@ -4922,6 +5660,40 @@ } } }, + "controller.api.services.v1.SetTargetCredentialLibrariesRequest": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "integer", + "format": "int64", + "description": "Version is used to ensure this resource has not changed.\nThe mutation will fail if the version does not match the latest known good version." + }, + "credential_library_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "credential_libraries": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.services.v1.CredentialLibrary" + } + } + }, + "description": "The ids from both credential_library_ids and credential_libraries are used.\nWhen using credential_library_ids the default value \"application\" is used\nfor the purpose." + }, + "controller.api.services.v1.SetTargetCredentialLibrariesResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.targets.v1.Target" + } + } + }, "controller.api.services.v1.SetTargetHostSetsRequest": { "type": "object", "properties": { @@ -4992,6 +5764,22 @@ } } }, + "controller.api.services.v1.UpdateCredentialLibraryResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentiallibraries.v1.CredentialLibrary" + } + } + }, + "controller.api.services.v1.UpdateCredentialStoreResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentialstores.v1.CredentialStore" + } + } + }, "controller.api.services.v1.UpdateGroupResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/resources/credentiallibraries/credential_library.pb.go b/internal/gen/controller/api/resources/credentiallibraries/credential_library.pb.go new file mode 100644 index 000000000..5f96d8b04 --- /dev/null +++ b/internal/gen/controller/api/resources/credentiallibraries/credential_library.pb.go @@ -0,0 +1,416 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/api/resources/credentiallibraries/v1/credential_library.proto + +package credentiallibraries + +import ( + scopes "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" + _ "github.com/hashicorp/boundary/internal/gen/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// CredentialLibrary contains all fields related to an Credential Library resource +type CredentialLibrary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Output only. The ID of the Credential Library. + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty"` + // The ID of the Credential Store of which this Credential Library is a part. + CredentialStoreId string `protobuf:"bytes,20,opt,name=credential_store_id,proto3" json:"credential_store_id,omitempty"` + // Output only. Scope information for this Credential Library. + Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` + // Optional name for identification purposes. + Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` + // Optional user-set description for identification purposes. + Description *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty"` + // Output only. The time this resource was created. + CreatedTime *timestamppb.Timestamp `protobuf:"bytes,60,opt,name=created_time,proto3" json:"created_time,omitempty"` + // Output only. The time this resource was last updated. + UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,70,opt,name=updated_time,proto3" json:"updated_time,omitempty"` + // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty"` + // The Credential Library type. + Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` + // The attributes that are applicable for the specific Credential Library type. + Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Output only. The available actions on this resource for this user. + AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` +} + +func (x *CredentialLibrary) Reset() { + *x = CredentialLibrary{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialLibrary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialLibrary) ProtoMessage() {} + +func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[0] + 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 CredentialLibrary.ProtoReflect.Descriptor instead. +func (*CredentialLibrary) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescGZIP(), []int{0} +} + +func (x *CredentialLibrary) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CredentialLibrary) GetCredentialStoreId() string { + if x != nil { + return x.CredentialStoreId + } + return "" +} + +func (x *CredentialLibrary) GetScope() *scopes.ScopeInfo { + if x != nil { + return x.Scope + } + return nil +} + +func (x *CredentialLibrary) GetName() *wrapperspb.StringValue { + if x != nil { + return x.Name + } + return nil +} + +func (x *CredentialLibrary) GetDescription() *wrapperspb.StringValue { + if x != nil { + return x.Description + } + return nil +} + +func (x *CredentialLibrary) GetCreatedTime() *timestamppb.Timestamp { + if x != nil { + return x.CreatedTime + } + return nil +} + +func (x *CredentialLibrary) GetUpdatedTime() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedTime + } + return nil +} + +func (x *CredentialLibrary) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *CredentialLibrary) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *CredentialLibrary) GetAttributes() *structpb.Struct { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *CredentialLibrary) GetAuthorizedActions() []string { + if x != nil { + return x.AuthorizedActions + } + return nil +} + +// The attributes of a vault typed Credential Library. +type VaultCredentialLibraryAttributes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The path in Vault to request credentials from. + Path *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=path,proto3" json:"path,omitempty"` + // The HTTP method the library uses to communicate with Vault. + HttpMethod *wrapperspb.StringValue `protobuf:"bytes,20,opt,name=http_method,proto3" json:"http_method,omitempty"` + // The body of the HTTP request the library sends to vault. When set http_method must be "POST" + HttpRequestBody *wrapperspb.StringValue `protobuf:"bytes,30,opt,name=http_request_body,proto3" json:"http_request_body,omitempty"` +} + +func (x *VaultCredentialLibraryAttributes) Reset() { + *x = VaultCredentialLibraryAttributes{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VaultCredentialLibraryAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VaultCredentialLibraryAttributes) ProtoMessage() {} + +func (x *VaultCredentialLibraryAttributes) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[1] + 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 VaultCredentialLibraryAttributes.ProtoReflect.Descriptor instead. +func (*VaultCredentialLibraryAttributes) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescGZIP(), []int{1} +} + +func (x *VaultCredentialLibraryAttributes) GetPath() *wrapperspb.StringValue { + if x != nil { + return x.Path + } + return nil +} + +func (x *VaultCredentialLibraryAttributes) GetHttpMethod() *wrapperspb.StringValue { + if x != nil { + return x.HttpMethod + } + return nil +} + +func (x *VaultCredentialLibraryAttributes) GetHttpRequestBody() *wrapperspb.StringValue { + if x != nil { + return x.HttpRequestBody + } + return nil +} + +var File_controller_api_resources_credentiallibraries_v1_credential_library_proto protoreflect.FileDescriptor + +var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc = []byte{ + 0x0a, 0x48, 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, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x76, + 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2f, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 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, 0x1a, 0x1c, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, + 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2e, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe4, 0x04, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x13, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x43, + 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 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, 0x42, + 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 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, 0x42, 0x22, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, + 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x22, 0xee, 0x02, + 0x0a, 0x20, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x12, 0x56, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x0a, 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, 0x42, 0x24, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x0f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x74, 0x68, 0x12, 0x09, 0x56, 0x61, 0x75, 0x6c, 0x74, + 0x50, 0x61, 0x74, 0x68, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x6c, 0x0a, 0x0b, 0x68, 0x74, + 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x14, 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, 0x42, 0x2c, 0xa0, + 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x24, 0x0a, 0x16, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, + 0x0a, 0x48, 0x74, 0x74, 0x70, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x52, 0x0b, 0x68, 0x74, 0x74, + 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x83, 0x01, 0x0a, 0x11, 0x68, 0x74, 0x74, + 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x1e, + 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, 0x42, 0x37, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2f, 0x0a, 0x1c, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x5f, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x0f, 0x48, 0x74, 0x74, 0x70, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x6f, 0x64, 0x79, 0x52, 0x11, 0x68, 0x74, 0x74, + 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x6d, + 0x5a, 0x6b, 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, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescOnce sync.Once + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData = file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc +) + +func file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescGZIP() []byte { + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescOnce.Do(func() { + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData) + }) + return file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDescData +} + +var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_goTypes = []interface{}{ + (*CredentialLibrary)(nil), // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary + (*VaultCredentialLibraryAttributes)(nil), // 1: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes + (*scopes.ScopeInfo)(nil), // 2: controller.api.resources.scopes.v1.ScopeInfo + (*wrapperspb.StringValue)(nil), // 3: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 4: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 5: google.protobuf.Struct +} +var file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs = []int32{ + 2, // 0: controller.api.resources.credentiallibraries.v1.CredentialLibrary.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 3, // 1: controller.api.resources.credentiallibraries.v1.CredentialLibrary.name:type_name -> google.protobuf.StringValue + 3, // 2: controller.api.resources.credentiallibraries.v1.CredentialLibrary.description:type_name -> google.protobuf.StringValue + 4, // 3: controller.api.resources.credentiallibraries.v1.CredentialLibrary.created_time:type_name -> google.protobuf.Timestamp + 4, // 4: controller.api.resources.credentiallibraries.v1.CredentialLibrary.updated_time:type_name -> google.protobuf.Timestamp + 5, // 5: controller.api.resources.credentiallibraries.v1.CredentialLibrary.attributes:type_name -> google.protobuf.Struct + 3, // 6: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.path:type_name -> google.protobuf.StringValue + 3, // 7: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_method:type_name -> google.protobuf.StringValue + 3, // 8: controller.api.resources.credentiallibraries.v1.VaultCredentialLibraryAttributes.http_request_body:type_name -> google.protobuf.StringValue + 9, // [9:9] is the sub-list for method output_type + 9, // [9:9] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name +} + +func init() { file_controller_api_resources_credentiallibraries_v1_credential_library_proto_init() } +func file_controller_api_resources_credentiallibraries_v1_credential_library_proto_init() { + if File_controller_api_resources_credentiallibraries_v1_credential_library_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialLibrary); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VaultCredentialLibraryAttributes); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_resources_credentiallibraries_v1_credential_library_proto_goTypes, + DependencyIndexes: file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs, + MessageInfos: file_controller_api_resources_credentiallibraries_v1_credential_library_proto_msgTypes, + }.Build() + File_controller_api_resources_credentiallibraries_v1_credential_library_proto = out.File + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_rawDesc = nil + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_goTypes = nil + file_controller_api_resources_credentiallibraries_v1_credential_library_proto_depIdxs = nil +} diff --git a/internal/gen/controller/api/resources/credentialstores/credential_store.pb.go b/internal/gen/controller/api/resources/credentialstores/credential_store.pb.go new file mode 100644 index 000000000..dd8994867 --- /dev/null +++ b/internal/gen/controller/api/resources/credentialstores/credential_store.pb.go @@ -0,0 +1,558 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/api/resources/credentialstores/v1/credential_store.proto + +package credentialstores + +import ( + scopes "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" + _ "github.com/hashicorp/boundary/internal/gen/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + structpb "google.golang.org/protobuf/types/known/structpb" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + wrapperspb "google.golang.org/protobuf/types/known/wrapperspb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// CredentialStore contains all fields related to an Credential Store resource +type CredentialStore struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Output only. The ID of the Credential Store. + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty"` + // The ID of the Scope of which this Credential Store is a part. + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,proto3" json:"scope_id,omitempty"` + // Output only. Scope information for this Credential Store. + Scope *scopes.ScopeInfo `protobuf:"bytes,30,opt,name=scope,proto3" json:"scope,omitempty"` + // Optional name for identification purposes. + Name *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=name,proto3" json:"name,omitempty"` + // Optional user-set description for identification purposes. + Description *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty"` + // Output only. The time this resource was created. + CreatedTime *timestamppb.Timestamp `protobuf:"bytes,60,opt,name=created_time,proto3" json:"created_time,omitempty"` + // Output only. The time this resource was last updated. + UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,70,opt,name=updated_time,proto3" json:"updated_time,omitempty"` + // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,80,opt,name=version,proto3" json:"version,omitempty"` + // The Credential Store type. + Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty"` + // The attributes that are applicable for the specific Credential Store type. + Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3" json:"attributes,omitempty"` + // Output only. The available actions on this resource for this user. + AuthorizedActions []string `protobuf:"bytes,300,rep,name=authorized_actions,proto3" json:"authorized_actions,omitempty"` + // Output only. The authorized actions for the scope's collections. + AuthorizedCollectionActions map[string]*structpb.ListValue `protobuf:"bytes,310,rep,name=authorized_collection_actions,proto3" json:"authorized_collection_actions,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *CredentialStore) Reset() { + *x = CredentialStore{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialStore) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialStore) ProtoMessage() {} + +func (x *CredentialStore) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[0] + 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 CredentialStore.ProtoReflect.Descriptor instead. +func (*CredentialStore) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescGZIP(), []int{0} +} + +func (x *CredentialStore) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CredentialStore) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *CredentialStore) GetScope() *scopes.ScopeInfo { + if x != nil { + return x.Scope + } + return nil +} + +func (x *CredentialStore) GetName() *wrapperspb.StringValue { + if x != nil { + return x.Name + } + return nil +} + +func (x *CredentialStore) GetDescription() *wrapperspb.StringValue { + if x != nil { + return x.Description + } + return nil +} + +func (x *CredentialStore) GetCreatedTime() *timestamppb.Timestamp { + if x != nil { + return x.CreatedTime + } + return nil +} + +func (x *CredentialStore) GetUpdatedTime() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedTime + } + return nil +} + +func (x *CredentialStore) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *CredentialStore) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *CredentialStore) GetAttributes() *structpb.Struct { + if x != nil { + return x.Attributes + } + return nil +} + +func (x *CredentialStore) GetAuthorizedActions() []string { + if x != nil { + return x.AuthorizedActions + } + return nil +} + +func (x *CredentialStore) GetAuthorizedCollectionActions() map[string]*structpb.ListValue { + if x != nil { + return x.AuthorizedCollectionActions + } + return nil +} + +// The attributes of a vault typed Credential Store. +type VaultCredentialStoreAttributes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The complete url address of vault. + Address *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=address,proto3" json:"address,omitempty"` + // The namespace of vault used by this store + Namespace *wrapperspb.StringValue `protobuf:"bytes,20,opt,name=namespace,proto3" json:"namespace,omitempty"` + // The PEM encoded CA cert to verify the vault server's ssl certificate + CaCert *wrapperspb.StringValue `protobuf:"bytes,30,opt,name=ca_cert,proto3" json:"ca_cert,omitempty"` + // The value to use as the SNI host when connecting to vault via TLS. + TlsServerName *wrapperspb.StringValue `protobuf:"bytes,40,opt,name=tls_server_name,proto3" json:"tls_server_name,omitempty"` + // When set to true verification of the TLS certificate is disabled. + TlsSkipVerify *wrapperspb.BoolValue `protobuf:"bytes,50,opt,name=tls_skip_verify,proto3" json:"tls_skip_verify,omitempty"` + // Input only. The current vault token used by this credential store for creating new credentials. + Token *wrapperspb.StringValue `protobuf:"bytes,60,opt,name=token,proto3" json:"token,omitempty"` + // Output only. The hmac value of the vault token used by this credential store. + TokenHmac string `protobuf:"bytes,70,opt,name=token_hmac,proto3" json:"token_hmac,omitempty"` + // Input only. A PEM encoded client certificate for vault with an + // optional private key included in the bundle. It is an error to include + // the private key in this bundle as well as setting the certificate_key + // field. + ClientCertificate *wrapperspb.StringValue `protobuf:"bytes,80,opt,name=client_certificate,proto3" json:"client_certificate,omitempty"` + // Input only. A client certificate private key. + ClientCertificateKey *wrapperspb.StringValue `protobuf:"bytes,90,opt,name=client_certificate_key,proto3" json:"client_certificate_key,omitempty"` + // Output only. The hmac value of the private key used by the credential store. + ClientCertificateKeyHmac string `protobuf:"bytes,100,opt,name=client_certificate_key_hmac,proto3" json:"client_certificate_key_hmac,omitempty"` +} + +func (x *VaultCredentialStoreAttributes) Reset() { + *x = VaultCredentialStoreAttributes{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *VaultCredentialStoreAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*VaultCredentialStoreAttributes) ProtoMessage() {} + +func (x *VaultCredentialStoreAttributes) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[1] + 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 VaultCredentialStoreAttributes.ProtoReflect.Descriptor instead. +func (*VaultCredentialStoreAttributes) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescGZIP(), []int{1} +} + +func (x *VaultCredentialStoreAttributes) GetAddress() *wrapperspb.StringValue { + if x != nil { + return x.Address + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetNamespace() *wrapperspb.StringValue { + if x != nil { + return x.Namespace + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetCaCert() *wrapperspb.StringValue { + if x != nil { + return x.CaCert + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetTlsServerName() *wrapperspb.StringValue { + if x != nil { + return x.TlsServerName + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetTlsSkipVerify() *wrapperspb.BoolValue { + if x != nil { + return x.TlsSkipVerify + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetToken() *wrapperspb.StringValue { + if x != nil { + return x.Token + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetTokenHmac() string { + if x != nil { + return x.TokenHmac + } + return "" +} + +func (x *VaultCredentialStoreAttributes) GetClientCertificate() *wrapperspb.StringValue { + if x != nil { + return x.ClientCertificate + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetClientCertificateKey() *wrapperspb.StringValue { + if x != nil { + return x.ClientCertificateKey + } + return nil +} + +func (x *VaultCredentialStoreAttributes) GetClientCertificateKeyHmac() string { + if x != nil { + return x.ClientCertificateKeyHmac + } + return "" +} + +var File_controller_api_resources_credentialstores_v1_credential_store_proto protoreflect.FileDescriptor + +var file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDesc = []byte{ + 0x0a, 0x43, 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, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2c, 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, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 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, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2e, 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, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe0, + 0x06, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, + 0x72, 0x65, 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, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, + 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 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, 0x42, + 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 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, 0x42, 0x22, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x3d, 0x0a, + 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x64, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, + 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x12, 0xa5, 0x01, + 0x0a, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, + 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, + 0xb6, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x5e, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, + 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1d, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x6a, 0x0a, 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x65, 0x64, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 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, 0x93, 0x08, 0x0a, 0x1e, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x12, 0x62, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x0a, 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, 0x42, 0x2a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x22, 0x0a, 0x12, 0x61, + 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x12, 0x0c, 0x56, 0x61, 0x75, 0x6c, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x65, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x14, 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, 0x42, 0x29, 0xa0, 0xda, 0x29, 0x01, 0xc2, + 0xdd, 0x29, 0x21, 0x0a, 0x14, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, + 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, + 0x70, 0x61, 0x63, 0x65, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, + 0x5c, 0x0a, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x18, 0x1e, 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, 0x42, 0x24, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1c, 0x0a, 0x12, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x12, 0x06, 0x43, 0x61, + 0x43, 0x65, 0x72, 0x74, 0x52, 0x07, 0x63, 0x61, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x12, 0x7b, 0x0a, + 0x0f, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x28, 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, 0x42, 0x33, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2b, 0x0a, 0x1a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x0d, 0x54, 0x6c, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x52, 0x0f, 0x74, 0x6c, 0x73, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x79, 0x0a, 0x0f, 0x74, 0x6c, + 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x32, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, + 0x33, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x2b, 0x0a, 0x1a, 0x61, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x0d, 0x54, 0x6c, 0x73, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, + 0x72, 0x69, 0x66, 0x79, 0x52, 0x0f, 0x74, 0x6c, 0x73, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, + 0x65, 0x72, 0x69, 0x66, 0x79, 0x12, 0x55, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x3c, + 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, 0x42, 0x21, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x19, 0x0a, 0x10, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x05, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0a, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x12, 0x82, 0x01, 0x0a, + 0x12, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x18, 0x50, 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, 0x42, 0x34, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x2c, 0x0a, 0x1d, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, + 0x12, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x12, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x12, 0x91, 0x01, 0x0a, 0x16, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x5a, 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, + 0x42, 0x3b, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x33, 0x0a, 0x21, 0x61, 0x74, 0x74, 0x72, + 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x0e, 0x43, + 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x16, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x40, 0x0a, 0x1b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x5f, + 0x68, 0x6d, 0x61, 0x63, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x1b, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, 0x67, 0x5a, 0x65, 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, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2f, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x3b, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescOnce sync.Once + file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescData = file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDesc +) + +func file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescGZIP() []byte { + file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescOnce.Do(func() { + file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescData) + }) + return file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDescData +} + +var file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_controller_api_resources_credentialstores_v1_credential_store_proto_goTypes = []interface{}{ + (*CredentialStore)(nil), // 0: controller.api.resources.credentialstores.v1.CredentialStore + (*VaultCredentialStoreAttributes)(nil), // 1: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes + nil, // 2: controller.api.resources.credentialstores.v1.CredentialStore.AuthorizedCollectionActionsEntry + (*scopes.ScopeInfo)(nil), // 3: controller.api.resources.scopes.v1.ScopeInfo + (*wrapperspb.StringValue)(nil), // 4: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp + (*structpb.Struct)(nil), // 6: google.protobuf.Struct + (*wrapperspb.BoolValue)(nil), // 7: google.protobuf.BoolValue + (*structpb.ListValue)(nil), // 8: google.protobuf.ListValue +} +var file_controller_api_resources_credentialstores_v1_credential_store_proto_depIdxs = []int32{ + 3, // 0: controller.api.resources.credentialstores.v1.CredentialStore.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 4, // 1: controller.api.resources.credentialstores.v1.CredentialStore.name:type_name -> google.protobuf.StringValue + 4, // 2: controller.api.resources.credentialstores.v1.CredentialStore.description:type_name -> google.protobuf.StringValue + 5, // 3: controller.api.resources.credentialstores.v1.CredentialStore.created_time:type_name -> google.protobuf.Timestamp + 5, // 4: controller.api.resources.credentialstores.v1.CredentialStore.updated_time:type_name -> google.protobuf.Timestamp + 6, // 5: controller.api.resources.credentialstores.v1.CredentialStore.attributes:type_name -> google.protobuf.Struct + 2, // 6: controller.api.resources.credentialstores.v1.CredentialStore.authorized_collection_actions:type_name -> controller.api.resources.credentialstores.v1.CredentialStore.AuthorizedCollectionActionsEntry + 4, // 7: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.address:type_name -> google.protobuf.StringValue + 4, // 8: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.namespace:type_name -> google.protobuf.StringValue + 4, // 9: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.ca_cert:type_name -> google.protobuf.StringValue + 4, // 10: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.tls_server_name:type_name -> google.protobuf.StringValue + 7, // 11: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.tls_skip_verify:type_name -> google.protobuf.BoolValue + 4, // 12: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.token:type_name -> google.protobuf.StringValue + 4, // 13: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.client_certificate:type_name -> google.protobuf.StringValue + 4, // 14: controller.api.resources.credentialstores.v1.VaultCredentialStoreAttributes.client_certificate_key:type_name -> google.protobuf.StringValue + 8, // 15: controller.api.resources.credentialstores.v1.CredentialStore.AuthorizedCollectionActionsEntry.value:type_name -> google.protobuf.ListValue + 16, // [16:16] is the sub-list for method output_type + 16, // [16:16] 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 +} + +func init() { file_controller_api_resources_credentialstores_v1_credential_store_proto_init() } +func file_controller_api_resources_credentialstores_v1_credential_store_proto_init() { + if File_controller_api_resources_credentialstores_v1_credential_store_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialStore); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*VaultCredentialStoreAttributes); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_resources_credentialstores_v1_credential_store_proto_goTypes, + DependencyIndexes: file_controller_api_resources_credentialstores_v1_credential_store_proto_depIdxs, + MessageInfos: file_controller_api_resources_credentialstores_v1_credential_store_proto_msgTypes, + }.Build() + File_controller_api_resources_credentialstores_v1_credential_store_proto = out.File + file_controller_api_resources_credentialstores_v1_credential_store_proto_rawDesc = nil + file_controller_api_resources_credentialstores_v1_credential_store_proto_goTypes = nil + file_controller_api_resources_credentialstores_v1_credential_store_proto_depIdxs = nil +} diff --git a/internal/gen/controller/api/resources/targets/target.pb.go b/internal/gen/controller/api/resources/targets/target.pb.go index 35c5fadc2..5a3937678 100644 --- a/internal/gen/controller/api/resources/targets/target.pb.go +++ b/internal/gen/controller/api/resources/targets/target.pb.go @@ -82,6 +82,157 @@ func (x *HostSet) GetHostCatalogId() string { return "" } +type CredentialLibrary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The ID of the Credential Library. + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty"` + // Output only. The name of the Credential Library. + Name string `protobuf:"bytes,20,opt,name=name,proto3" json:"name,omitempty"` + // Output only. The description of the Credential Library. + Description string `protobuf:"bytes,30,opt,name=description,proto3" json:"description,omitempty"` + // Output only. The Credential Store to which this Credential Library belongs. + CredentialStoreId string `protobuf:"bytes,40,opt,name=credential_store_id,proto3" json:"credential_store_id,omitempty"` + // The library's purpose for this target. + Purpose string `protobuf:"bytes,50,opt,name=purpose,proto3" json:"purpose,omitempty"` + // Output only. The type of the credential library. + Type string `protobuf:"bytes,60,opt,name=type,proto3" json:"type,omitempty"` +} + +func (x *CredentialLibrary) Reset() { + *x = CredentialLibrary{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialLibrary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialLibrary) ProtoMessage() {} + +func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[1] + 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 CredentialLibrary.ProtoReflect.Descriptor instead. +func (*CredentialLibrary) Descriptor() ([]byte, []int) { + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{1} +} + +func (x *CredentialLibrary) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CredentialLibrary) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *CredentialLibrary) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *CredentialLibrary) GetCredentialStoreId() string { + if x != nil { + return x.CredentialStoreId + } + return "" +} + +func (x *CredentialLibrary) GetPurpose() string { + if x != nil { + return x.Purpose + } + return "" +} + +func (x *CredentialLibrary) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +// A credential for a session. +type SessionCredential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Output only. The library which generated this credential. + CredentialLibrary *CredentialLibrary `protobuf:"bytes,1,opt,name=credential_library,json=credentialLibrary,proto3" json:"credential_library,omitempty"` + // Output only. The secret of this credential base64 encoded. + Secret string `protobuf:"bytes,2,opt,name=secret,proto3" json:"secret,omitempty"` +} + +func (x *SessionCredential) Reset() { + *x = SessionCredential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SessionCredential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SessionCredential) ProtoMessage() {} + +func (x *SessionCredential) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_targets_v1_target_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 SessionCredential.ProtoReflect.Descriptor instead. +func (*SessionCredential) Descriptor() ([]byte, []int) { + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{2} +} + +func (x *SessionCredential) GetCredentialLibrary() *CredentialLibrary { + if x != nil { + return x.CredentialLibrary + } + return nil +} + +func (x *SessionCredential) GetSecret() string { + if x != nil { + return x.Secret + } + return "" +} + // Target contains all fields related to a Target resource type Target struct { state protoimpl.MessageState @@ -117,6 +268,10 @@ type Target struct { SessionConnectionLimit *wrapperspb.Int32Value `protobuf:"bytes,130,opt,name=session_connection_limit,proto3" json:"session_connection_limit,omitempty"` // Optional boolean expression to filter the workers that are allowed to satisfy this request. WorkerFilter *wrapperspb.StringValue `protobuf:"bytes,140,opt,name=worker_filter,proto3" json:"worker_filter,omitempty"` + // The IDs of the credential libraries associated with this Target. + CredentialLibraryIds []string `protobuf:"bytes,150,rep,name=credential_library_ids,proto3" json:"credential_library_ids,omitempty"` + // Output only. The credential libraries associated with this Target. + CredentialLibraries []*CredentialLibrary `protobuf:"bytes,160,rep,name=credential_libraries,proto3" json:"credential_libraries,omitempty"` // The attributes that are applicable for the specific Target. Attributes *structpb.Struct `protobuf:"bytes,200,opt,name=attributes,proto3" json:"attributes,omitempty"` // Output only. The available actions on this resource for this user. @@ -126,7 +281,7 @@ type Target struct { func (x *Target) Reset() { *x = Target{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[1] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -139,7 +294,7 @@ func (x *Target) String() string { func (*Target) ProtoMessage() {} func (x *Target) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[1] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -152,7 +307,7 @@ func (x *Target) ProtoReflect() protoreflect.Message { // Deprecated: Use Target.ProtoReflect.Descriptor instead. func (*Target) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{1} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{3} } func (x *Target) GetId() string { @@ -253,6 +408,20 @@ func (x *Target) GetWorkerFilter() *wrapperspb.StringValue { return nil } +func (x *Target) GetCredentialLibraryIds() []string { + if x != nil { + return x.CredentialLibraryIds + } + return nil +} + +func (x *Target) GetCredentialLibraries() []*CredentialLibrary { + if x != nil { + return x.CredentialLibraries + } + return nil +} + func (x *Target) GetAttributes() *structpb.Struct { if x != nil { return x.Attributes @@ -280,7 +449,7 @@ type TcpTargetAttributes struct { func (x *TcpTargetAttributes) Reset() { *x = TcpTargetAttributes{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[2] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -293,7 +462,7 @@ func (x *TcpTargetAttributes) String() string { func (*TcpTargetAttributes) ProtoMessage() {} func (x *TcpTargetAttributes) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[2] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -306,7 +475,7 @@ func (x *TcpTargetAttributes) ProtoReflect() protoreflect.Message { // Deprecated: Use TcpTargetAttributes.ProtoReflect.Descriptor instead. func (*TcpTargetAttributes) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{2} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{4} } func (x *TcpTargetAttributes) GetDefaultPort() *wrapperspb.UInt32Value { @@ -329,7 +498,7 @@ type WorkerInfo struct { func (x *WorkerInfo) Reset() { *x = WorkerInfo{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[3] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -342,7 +511,7 @@ func (x *WorkerInfo) String() string { func (*WorkerInfo) ProtoMessage() {} func (x *WorkerInfo) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[3] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -355,7 +524,7 @@ func (x *WorkerInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use WorkerInfo.ProtoReflect.Descriptor instead. func (*WorkerInfo) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{3} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{5} } func (x *WorkerInfo) GetAddress() string { @@ -398,7 +567,7 @@ type SessionAuthorizationData struct { func (x *SessionAuthorizationData) Reset() { *x = SessionAuthorizationData{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[4] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -411,7 +580,7 @@ func (x *SessionAuthorizationData) String() string { func (*SessionAuthorizationData) ProtoMessage() {} func (x *SessionAuthorizationData) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[4] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -424,7 +593,7 @@ func (x *SessionAuthorizationData) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAuthorizationData.ProtoReflect.Descriptor instead. func (*SessionAuthorizationData) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{4} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{6} } func (x *SessionAuthorizationData) GetSessionId() string { @@ -530,12 +699,14 @@ type SessionAuthorization struct { AuthorizationToken string `protobuf:"bytes,90,opt,name=authorization_token,proto3" json:"authorization_token,omitempty"` // Output only. The endpoint address that the worker will connect to, useful for setting TLS parameters. Endpoint string `protobuf:"bytes,100,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + // Output only. The credentials for this session. + Credentials []*SessionCredential `protobuf:"bytes,110,rep,name=credentials,proto3" json:"credentials,omitempty"` } func (x *SessionAuthorization) Reset() { *x = SessionAuthorization{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[5] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -548,7 +719,7 @@ func (x *SessionAuthorization) String() string { func (*SessionAuthorization) ProtoMessage() {} func (x *SessionAuthorization) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[5] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -561,7 +732,7 @@ func (x *SessionAuthorization) ProtoReflect() protoreflect.Message { // Deprecated: Use SessionAuthorization.ProtoReflect.Descriptor instead. func (*SessionAuthorization) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{5} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{7} } func (x *SessionAuthorization) GetSessionId() string { @@ -634,6 +805,13 @@ func (x *SessionAuthorization) GetEndpoint() string { return "" } +func (x *SessionAuthorization) GetCredentials() []*SessionCredential { + if x != nil { + return x.Credentials + } + return nil +} + var File_controller_api_resources_targets_v1_target_proto protoreflect.FileDescriptor var file_controller_api_resources_targets_v1_target_proto_rawDesc = []byte{ @@ -658,148 +836,185 @@ var file_controller_api_resources_targets_v1_target_proto_rawDesc = []byte{ 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x28, 0x0a, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x63, 0x61, 0x74, - 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x22, 0xba, 0x08, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 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, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, - 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, + 0x61, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x64, 0x22, 0xb9, 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, + 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x12, 0x65, 0x0a, 0x12, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x11, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x22, 0xe0, 0x09, 0x0a, 0x06, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 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, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, + 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x28, 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, + 0x42, 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 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, 0x42, - 0x14, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x62, 0x0a, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, + 0x22, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x12, 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x18, 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, + 0x70, 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x22, + 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x73, 0x12, 0x4a, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, + 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, + 0x53, 0x65, 0x74, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x12, 0x80, + 0x01, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x78, 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, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, + 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, + 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x11, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x12, 0x94, 0x01, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x82, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x42, 0x3a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x32, 0x0a, 0x18, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x18, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x6a, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, + 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x8c, 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, 0x42, 0x22, - 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x3e, 0x0a, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, - 0x46, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x22, 0x0a, - 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x64, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, - 0x73, 0x12, 0x4a, 0x0a, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x6e, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 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, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x48, 0x6f, 0x73, 0x74, 0x53, - 0x65, 0x74, 0x52, 0x09, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x12, 0x80, 0x01, - 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, - 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x78, 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, 0x42, 0x30, 0xa0, 0xda, 0x29, 0x01, 0xc2, - 0xdd, 0x29, 0x28, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, - 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x11, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x52, 0x13, 0x73, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, - 0x12, 0x94, 0x01, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x82, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x42, 0x3a, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x32, 0x0a, 0x18, 0x73, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, - 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x18, 0x73, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x6a, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x8c, 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, 0x42, 0x25, 0xa0, - 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, - 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, - 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, - 0x73, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, - 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0a, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, - 0x74, 0x65, 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, 0x22, 0x87, 0x01, 0x0a, 0x13, 0x54, 0x63, 0x70, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x70, 0x0a, 0x0c, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x0a, 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, - 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x26, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, - 0x6f, 0x72, 0x74, 0x12, 0x0b, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, - 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x26, - 0x0a, 0x0a, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xed, 0x03, 0x0a, 0x18, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, - 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, - 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x2d, 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, 0x73, 0x63, 0x6f, 0x70, - 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x50, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x5a, - 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x78, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x65, 0x72, - 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x21, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, - 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, - 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x68, - 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, - 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, - 0x74, 0x18, 0x8d, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, - 0x6e, 0x74, 0x12, 0x52, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, - 0x6f, 0x18, 0x96, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, - 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, - 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x91, 0x03, 0x0a, 0x14, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, - 0x1c, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, - 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x63, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x25, + 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, + 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x37, 0x0a, 0x16, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x96, + 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x6b, 0x0a, + 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0xa0, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, - 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, - 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x32, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, - 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x18, - 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x30, 0x0a, 0x13, - 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x61, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, - 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x42, 0x55, 0x5a, 0x53, 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, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x73, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x3b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x52, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3e, 0x0a, 0x0a, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x42, 0x04, 0xa0, 0xda, 0x29, 0x01, 0x52, 0x0a, + 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 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, 0x22, 0x87, 0x01, 0x0a, 0x13, + 0x54, 0x63, 0x70, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x12, 0x70, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, + 0x6f, 0x72, 0x74, 0x18, 0x0a, 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, 0x42, 0x2e, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, + 0x26, 0x0a, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, + 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x0b, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x26, 0x0a, 0x0a, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0xed, 0x03, + 0x0a, 0x18, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, + 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, + 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x3e, 0x0a, + 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, + 0x0c, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x12, 0x0a, + 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x20, 0x0a, + 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x78, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, + 0x21, 0x0a, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x82, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6b, + 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x8c, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x68, 0x6f, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x08, + 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x8d, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x52, 0x0a, 0x0b, 0x77, 0x6f, 0x72, + 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x96, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x2f, 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, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, + 0x52, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0xeb, 0x03, + 0x0a, 0x14, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x5f, 0x69, 0x64, 0x12, 0x43, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x2d, 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, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x6e, + 0x66, 0x6f, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0c, 0x63, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x32, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x72, + 0x5f, 0x69, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x73, + 0x65, 0x74, 0x5f, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x46, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x5a, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x18, 0x64, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x12, 0x58, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, + 0x18, 0x6e, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x0b, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x42, 0x55, 0x5a, 0x53, 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, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x73, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x3b, 0x74, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -814,43 +1029,48 @@ func file_controller_api_resources_targets_v1_target_proto_rawDescGZIP() []byte return file_controller_api_resources_targets_v1_target_proto_rawDescData } -var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 6) +var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 8) var file_controller_api_resources_targets_v1_target_proto_goTypes = []interface{}{ (*HostSet)(nil), // 0: controller.api.resources.targets.v1.HostSet - (*Target)(nil), // 1: controller.api.resources.targets.v1.Target - (*TcpTargetAttributes)(nil), // 2: controller.api.resources.targets.v1.TcpTargetAttributes - (*WorkerInfo)(nil), // 3: controller.api.resources.targets.v1.WorkerInfo - (*SessionAuthorizationData)(nil), // 4: controller.api.resources.targets.v1.SessionAuthorizationData - (*SessionAuthorization)(nil), // 5: controller.api.resources.targets.v1.SessionAuthorization - (*scopes.ScopeInfo)(nil), // 6: controller.api.resources.scopes.v1.ScopeInfo - (*wrapperspb.StringValue)(nil), // 7: google.protobuf.StringValue - (*timestamppb.Timestamp)(nil), // 8: google.protobuf.Timestamp - (*wrapperspb.UInt32Value)(nil), // 9: google.protobuf.UInt32Value - (*wrapperspb.Int32Value)(nil), // 10: google.protobuf.Int32Value - (*structpb.Struct)(nil), // 11: google.protobuf.Struct + (*CredentialLibrary)(nil), // 1: controller.api.resources.targets.v1.CredentialLibrary + (*SessionCredential)(nil), // 2: controller.api.resources.targets.v1.SessionCredential + (*Target)(nil), // 3: controller.api.resources.targets.v1.Target + (*TcpTargetAttributes)(nil), // 4: controller.api.resources.targets.v1.TcpTargetAttributes + (*WorkerInfo)(nil), // 5: controller.api.resources.targets.v1.WorkerInfo + (*SessionAuthorizationData)(nil), // 6: controller.api.resources.targets.v1.SessionAuthorizationData + (*SessionAuthorization)(nil), // 7: controller.api.resources.targets.v1.SessionAuthorization + (*scopes.ScopeInfo)(nil), // 8: controller.api.resources.scopes.v1.ScopeInfo + (*wrapperspb.StringValue)(nil), // 9: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp + (*wrapperspb.UInt32Value)(nil), // 11: google.protobuf.UInt32Value + (*wrapperspb.Int32Value)(nil), // 12: google.protobuf.Int32Value + (*structpb.Struct)(nil), // 13: google.protobuf.Struct } var file_controller_api_resources_targets_v1_target_proto_depIdxs = []int32{ - 6, // 0: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 7, // 1: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue - 7, // 2: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue - 8, // 3: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp - 8, // 4: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp - 0, // 5: controller.api.resources.targets.v1.Target.host_sets:type_name -> controller.api.resources.targets.v1.HostSet - 9, // 6: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value - 10, // 7: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value - 7, // 8: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue - 11, // 9: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct - 9, // 10: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value - 6, // 11: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 8, // 12: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp - 3, // 13: controller.api.resources.targets.v1.SessionAuthorizationData.worker_info:type_name -> controller.api.resources.targets.v1.WorkerInfo - 6, // 14: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 8, // 15: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp - 16, // [16:16] is the sub-list for method output_type - 16, // [16:16] 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 + 1, // 0: controller.api.resources.targets.v1.SessionCredential.credential_library:type_name -> controller.api.resources.targets.v1.CredentialLibrary + 8, // 1: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 9, // 2: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue + 9, // 3: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue + 10, // 4: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp + 10, // 5: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp + 0, // 6: controller.api.resources.targets.v1.Target.host_sets:type_name -> controller.api.resources.targets.v1.HostSet + 11, // 7: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value + 12, // 8: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value + 9, // 9: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue + 1, // 10: controller.api.resources.targets.v1.Target.credential_libraries:type_name -> controller.api.resources.targets.v1.CredentialLibrary + 13, // 11: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct + 11, // 12: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value + 8, // 13: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 10, // 14: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp + 5, // 15: controller.api.resources.targets.v1.SessionAuthorizationData.worker_info:type_name -> controller.api.resources.targets.v1.WorkerInfo + 8, // 16: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 10, // 17: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp + 2, // 18: controller.api.resources.targets.v1.SessionAuthorization.credentials:type_name -> controller.api.resources.targets.v1.SessionCredential + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_controller_api_resources_targets_v1_target_proto_init() } @@ -872,7 +1092,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { } } file_controller_api_resources_targets_v1_target_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Target); i { + switch v := v.(*CredentialLibrary); i { case 0: return &v.state case 1: @@ -884,7 +1104,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { } } file_controller_api_resources_targets_v1_target_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TcpTargetAttributes); i { + switch v := v.(*SessionCredential); i { case 0: return &v.state case 1: @@ -896,7 +1116,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { } } file_controller_api_resources_targets_v1_target_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkerInfo); i { + switch v := v.(*Target); i { case 0: return &v.state case 1: @@ -908,7 +1128,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { } } file_controller_api_resources_targets_v1_target_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SessionAuthorizationData); i { + switch v := v.(*TcpTargetAttributes); i { case 0: return &v.state case 1: @@ -920,6 +1140,30 @@ func file_controller_api_resources_targets_v1_target_proto_init() { } } file_controller_api_resources_targets_v1_target_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*WorkerInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_resources_targets_v1_target_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SessionAuthorizationData); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_resources_targets_v1_target_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*SessionAuthorization); i { case 0: return &v.state @@ -938,7 +1182,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_resources_targets_v1_target_proto_rawDesc, NumEnums: 0, - NumMessages: 6, + NumMessages: 8, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/gen/controller/api/services/credential_library_service.pb.go b/internal/gen/controller/api/services/credential_library_service.pb.go new file mode 100644 index 000000000..5ac6b5ca8 --- /dev/null +++ b/internal/gen/controller/api/services/credential_library_service.pb.go @@ -0,0 +1,884 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/api/services/v1/credential_library_service.proto + +package services + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + credentiallibraries "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetCredentialLibraryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetCredentialLibraryRequest) Reset() { + *x = GetCredentialLibraryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialLibraryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialLibraryRequest) ProtoMessage() {} + +func (x *GetCredentialLibraryRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[0] + 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 GetCredentialLibraryRequest.ProtoReflect.Descriptor instead. +func (*GetCredentialLibraryRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetCredentialLibraryRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetCredentialLibraryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentiallibraries.CredentialLibrary `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *GetCredentialLibraryResponse) Reset() { + *x = GetCredentialLibraryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialLibraryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialLibraryResponse) ProtoMessage() {} + +func (x *GetCredentialLibraryResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[1] + 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 GetCredentialLibraryResponse.ProtoReflect.Descriptor instead. +func (*GetCredentialLibraryResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCredentialLibraryResponse) GetItem() *credentiallibraries.CredentialLibrary { + if x != nil { + return x.Item + } + return nil +} + +type ListCredentialLibrariesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + CredentialStoreId string `protobuf:"bytes,1,opt,name=credential_store_id,json=scope_id,proto3" json:"credential_store_id,omitempty"` + Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListCredentialLibrariesRequest) Reset() { + *x = ListCredentialLibrariesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialLibrariesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialLibrariesRequest) ProtoMessage() {} + +func (x *ListCredentialLibrariesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_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 ListCredentialLibrariesRequest.ProtoReflect.Descriptor instead. +func (*ListCredentialLibrariesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListCredentialLibrariesRequest) GetCredentialStoreId() string { + if x != nil { + return x.CredentialStoreId + } + return "" +} + +func (x *ListCredentialLibrariesRequest) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +type ListCredentialLibrariesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*credentiallibraries.CredentialLibrary `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ListCredentialLibrariesResponse) Reset() { + *x = ListCredentialLibrariesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialLibrariesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialLibrariesResponse) ProtoMessage() {} + +func (x *ListCredentialLibrariesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[3] + 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 ListCredentialLibrariesResponse.ProtoReflect.Descriptor instead. +func (*ListCredentialLibrariesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ListCredentialLibrariesResponse) GetItems() []*credentiallibraries.CredentialLibrary { + if x != nil { + return x.Items + } + return nil +} + +type CreateCredentialLibraryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentiallibraries.CredentialLibrary `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialLibraryRequest) Reset() { + *x = CreateCredentialLibraryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialLibraryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialLibraryRequest) ProtoMessage() {} + +func (x *CreateCredentialLibraryRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[4] + 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 CreateCredentialLibraryRequest.ProtoReflect.Descriptor instead. +func (*CreateCredentialLibraryRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateCredentialLibraryRequest) GetItem() *credentiallibraries.CredentialLibrary { + if x != nil { + return x.Item + } + return nil +} + +type CreateCredentialLibraryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` + Item *credentiallibraries.CredentialLibrary `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialLibraryResponse) Reset() { + *x = CreateCredentialLibraryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialLibraryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialLibraryResponse) ProtoMessage() {} + +func (x *CreateCredentialLibraryResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[5] + 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 CreateCredentialLibraryResponse.ProtoReflect.Descriptor instead. +func (*CreateCredentialLibraryResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateCredentialLibraryResponse) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *CreateCredentialLibraryResponse) GetItem() *credentiallibraries.CredentialLibrary { + if x != nil { + return x.Item + } + return nil +} + +type UpdateCredentialLibraryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Item *credentiallibraries.CredentialLibrary `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,proto3" json:"update_mask,omitempty"` +} + +func (x *UpdateCredentialLibraryRequest) Reset() { + *x = UpdateCredentialLibraryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialLibraryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialLibraryRequest) ProtoMessage() {} + +func (x *UpdateCredentialLibraryRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_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 UpdateCredentialLibraryRequest.ProtoReflect.Descriptor instead. +func (*UpdateCredentialLibraryRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateCredentialLibraryRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateCredentialLibraryRequest) GetItem() *credentiallibraries.CredentialLibrary { + if x != nil { + return x.Item + } + return nil +} + +func (x *UpdateCredentialLibraryRequest) GetUpdateMask() *fieldmaskpb.FieldMask { + if x != nil { + return x.UpdateMask + } + return nil +} + +type UpdateCredentialLibraryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentiallibraries.CredentialLibrary `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *UpdateCredentialLibraryResponse) Reset() { + *x = UpdateCredentialLibraryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialLibraryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialLibraryResponse) ProtoMessage() {} + +func (x *UpdateCredentialLibraryResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_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 UpdateCredentialLibraryResponse.ProtoReflect.Descriptor instead. +func (*UpdateCredentialLibraryResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateCredentialLibraryResponse) GetItem() *credentiallibraries.CredentialLibrary { + if x != nil { + return x.Item + } + return nil +} + +type DeleteCredentialLibraryRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteCredentialLibraryRequest) Reset() { + *x = DeleteCredentialLibraryRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialLibraryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialLibraryRequest) ProtoMessage() {} + +func (x *DeleteCredentialLibraryRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[8] + 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 DeleteCredentialLibraryRequest.ProtoReflect.Descriptor instead. +func (*DeleteCredentialLibraryRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteCredentialLibraryRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeleteCredentialLibraryResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteCredentialLibraryResponse) Reset() { + *x = DeleteCredentialLibraryResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialLibraryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialLibraryResponse) ProtoMessage() {} + +func (x *DeleteCredentialLibraryResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_library_service_proto_msgTypes[9] + 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 DeleteCredentialLibraryResponse.ProtoReflect.Descriptor instead. +func (*DeleteCredentialLibraryResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP(), []int{9} +} + +var File_controller_api_services_v1_credential_library_service_proto protoreflect.FileDescriptor + +var file_controller_api_services_v1_credential_library_service_proto_rawDesc = []byte{ + 0x0a, 0x3b, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 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, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x2d, 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, + 0x61, 0x73, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x48, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x1b, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x69, 0x64, 0x22, 0x76, 0x0a, 0x1c, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x56, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x42, 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, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x5f, 0x0a, 0x1e, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x13, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, + 0x5f, 0x69, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x7b, 0x0a, 0x1f, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x58, + 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x78, 0x0a, 0x1e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x56, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x22, 0x8b, 0x01, 0x0a, 0x1f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 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, 0x56, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0xc6, 0x01, 0x0a, 0x1e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x56, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x42, 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, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x79, 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, 0x79, 0x0a, 0x1f, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0x30, 0x0a, 0x1e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x21, 0x0a, 0x1f, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x85, 0x09, 0x0a, 0x18, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xdc, 0x01, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, + 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, 0x47, 0x65, 0x74, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x51, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x25, 0x12, 0x1d, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x69, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, + 0x41, 0x23, 0x12, 0x21, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x2e, 0x12, 0xd6, 0x01, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, + 0x73, 0x12, 0x3a, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 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, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x42, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x1a, 0x12, 0x18, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x92, 0x41, 0x1f, 0x12, + 0x1d, 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x12, 0xe9, + 0x01, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x3a, 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, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 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, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x55, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x26, 0x22, 0x18, 0x2f, 0x76, 0x31, + 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x92, 0x41, 0x26, 0x12, 0x24, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x20, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x2e, 0x12, 0xe7, 0x01, 0x0a, 0x17, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, 0x3a, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x3b, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x53, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2b, 0x32, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x92, 0x41, 0x1f, 0x12, 0x1d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x2e, 0x12, 0xda, 0x01, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x12, 0x3a, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3b, 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, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x1f, 0x2a, 0x1d, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x92, 0x41, 0x1e, 0x12, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 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 ( + file_controller_api_services_v1_credential_library_service_proto_rawDescOnce sync.Once + file_controller_api_services_v1_credential_library_service_proto_rawDescData = file_controller_api_services_v1_credential_library_service_proto_rawDesc +) + +func file_controller_api_services_v1_credential_library_service_proto_rawDescGZIP() []byte { + file_controller_api_services_v1_credential_library_service_proto_rawDescOnce.Do(func() { + file_controller_api_services_v1_credential_library_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_credential_library_service_proto_rawDescData) + }) + return file_controller_api_services_v1_credential_library_service_proto_rawDescData +} + +var file_controller_api_services_v1_credential_library_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_controller_api_services_v1_credential_library_service_proto_goTypes = []interface{}{ + (*GetCredentialLibraryRequest)(nil), // 0: controller.api.services.v1.GetCredentialLibraryRequest + (*GetCredentialLibraryResponse)(nil), // 1: controller.api.services.v1.GetCredentialLibraryResponse + (*ListCredentialLibrariesRequest)(nil), // 2: controller.api.services.v1.ListCredentialLibrariesRequest + (*ListCredentialLibrariesResponse)(nil), // 3: controller.api.services.v1.ListCredentialLibrariesResponse + (*CreateCredentialLibraryRequest)(nil), // 4: controller.api.services.v1.CreateCredentialLibraryRequest + (*CreateCredentialLibraryResponse)(nil), // 5: controller.api.services.v1.CreateCredentialLibraryResponse + (*UpdateCredentialLibraryRequest)(nil), // 6: controller.api.services.v1.UpdateCredentialLibraryRequest + (*UpdateCredentialLibraryResponse)(nil), // 7: controller.api.services.v1.UpdateCredentialLibraryResponse + (*DeleteCredentialLibraryRequest)(nil), // 8: controller.api.services.v1.DeleteCredentialLibraryRequest + (*DeleteCredentialLibraryResponse)(nil), // 9: controller.api.services.v1.DeleteCredentialLibraryResponse + (*credentiallibraries.CredentialLibrary)(nil), // 10: controller.api.resources.credentiallibraries.v1.CredentialLibrary + (*fieldmaskpb.FieldMask)(nil), // 11: google.protobuf.FieldMask +} +var file_controller_api_services_v1_credential_library_service_proto_depIdxs = []int32{ + 10, // 0: controller.api.services.v1.GetCredentialLibraryResponse.item:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 10, // 1: controller.api.services.v1.ListCredentialLibrariesResponse.items:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 10, // 2: controller.api.services.v1.CreateCredentialLibraryRequest.item:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 10, // 3: controller.api.services.v1.CreateCredentialLibraryResponse.item:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 10, // 4: controller.api.services.v1.UpdateCredentialLibraryRequest.item:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 11, // 5: controller.api.services.v1.UpdateCredentialLibraryRequest.update_mask:type_name -> google.protobuf.FieldMask + 10, // 6: controller.api.services.v1.UpdateCredentialLibraryResponse.item:type_name -> controller.api.resources.credentiallibraries.v1.CredentialLibrary + 0, // 7: controller.api.services.v1.CredentialLibraryService.GetCredentialLibrary:input_type -> controller.api.services.v1.GetCredentialLibraryRequest + 2, // 8: controller.api.services.v1.CredentialLibraryService.ListCredentialLibraries:input_type -> controller.api.services.v1.ListCredentialLibrariesRequest + 4, // 9: controller.api.services.v1.CredentialLibraryService.CreateCredentialLibrary:input_type -> controller.api.services.v1.CreateCredentialLibraryRequest + 6, // 10: controller.api.services.v1.CredentialLibraryService.UpdateCredentialLibrary:input_type -> controller.api.services.v1.UpdateCredentialLibraryRequest + 8, // 11: controller.api.services.v1.CredentialLibraryService.DeleteCredentialLibrary:input_type -> controller.api.services.v1.DeleteCredentialLibraryRequest + 1, // 12: controller.api.services.v1.CredentialLibraryService.GetCredentialLibrary:output_type -> controller.api.services.v1.GetCredentialLibraryResponse + 3, // 13: controller.api.services.v1.CredentialLibraryService.ListCredentialLibraries:output_type -> controller.api.services.v1.ListCredentialLibrariesResponse + 5, // 14: controller.api.services.v1.CredentialLibraryService.CreateCredentialLibrary:output_type -> controller.api.services.v1.CreateCredentialLibraryResponse + 7, // 15: controller.api.services.v1.CredentialLibraryService.UpdateCredentialLibrary:output_type -> controller.api.services.v1.UpdateCredentialLibraryResponse + 9, // 16: controller.api.services.v1.CredentialLibraryService.DeleteCredentialLibrary:output_type -> controller.api.services.v1.DeleteCredentialLibraryResponse + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_controller_api_services_v1_credential_library_service_proto_init() } +func file_controller_api_services_v1_credential_library_service_proto_init() { + if File_controller_api_services_v1_credential_library_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_services_v1_credential_library_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialLibraryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialLibraryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialLibrariesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialLibrariesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialLibraryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialLibraryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialLibraryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialLibraryResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialLibraryRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_library_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialLibraryResponse); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_services_v1_credential_library_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_controller_api_services_v1_credential_library_service_proto_goTypes, + DependencyIndexes: file_controller_api_services_v1_credential_library_service_proto_depIdxs, + MessageInfos: file_controller_api_services_v1_credential_library_service_proto_msgTypes, + }.Build() + File_controller_api_services_v1_credential_library_service_proto = out.File + file_controller_api_services_v1_credential_library_service_proto_rawDesc = nil + file_controller_api_services_v1_credential_library_service_proto_goTypes = nil + file_controller_api_services_v1_credential_library_service_proto_depIdxs = nil +} diff --git a/internal/gen/controller/api/services/credential_library_service.pb.gw.go b/internal/gen/controller/api/services/credential_library_service.pb.gw.go new file mode 100644 index 000000000..2a464b9b7 --- /dev/null +++ b/internal/gen/controller/api/services/credential_library_service.pb.gw.go @@ -0,0 +1,622 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: controller/api/services/v1/credential_library_service.proto + +/* +Package services is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package services + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_CredentialLibraryService_GetCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialLibraryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialLibraryRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.GetCredentialLibrary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialLibraryService_GetCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialLibraryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialLibraryRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.GetCredentialLibrary(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialLibraryService_ListCredentialLibraries_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_CredentialLibraryService_ListCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialLibraryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialLibrariesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialLibraryService_ListCredentialLibraries_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListCredentialLibraries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialLibraryService_ListCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialLibraryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialLibrariesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialLibraryService_ListCredentialLibraries_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListCredentialLibraries(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialLibraryService_CreateCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialLibraryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialLibraryRequest + 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.CreateCredentialLibrary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialLibraryService_CreateCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialLibraryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialLibraryRequest + 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.CreateCredentialLibrary(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialLibraryService_UpdateCredentialLibrary_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}} +) + +func request_CredentialLibraryService_UpdateCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialLibraryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialLibraryRequest + 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) + } + if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 { + if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Item); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } else { + protoReq.UpdateMask = fieldMask + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialLibraryService_UpdateCredentialLibrary_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateCredentialLibrary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialLibraryService_UpdateCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialLibraryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialLibraryRequest + 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) + } + if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 { + if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Item); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } else { + protoReq.UpdateMask = fieldMask + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialLibraryService_UpdateCredentialLibrary_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateCredentialLibrary(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialLibraryService_DeleteCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialLibraryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialLibraryRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.DeleteCredentialLibrary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialLibraryService_DeleteCredentialLibrary_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialLibraryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialLibraryRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.DeleteCredentialLibrary(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterCredentialLibraryServiceHandlerServer registers the http handlers for service CredentialLibraryService to "mux". +// UnaryRPC :call CredentialLibraryServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterCredentialLibraryServiceHandlerFromEndpoint instead. +func RegisterCredentialLibraryServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server CredentialLibraryServiceServer) error { + + mux.Handle("GET", pattern_CredentialLibraryService_GetCredentialLibrary_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/GetCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialLibraryService_GetCredentialLibrary_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_GetCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_GetCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialLibraryService_ListCredentialLibraries_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/ListCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialLibraryService_ListCredentialLibraries_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_ListCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialLibraryService_CreateCredentialLibrary_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/CreateCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialLibraryService_CreateCredentialLibrary_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_CreateCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_CreateCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialLibraryService_UpdateCredentialLibrary_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/UpdateCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialLibraryService_UpdateCredentialLibrary_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_UpdateCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_UpdateCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialLibraryService_DeleteCredentialLibrary_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/DeleteCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialLibraryService_DeleteCredentialLibrary_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_DeleteCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterCredentialLibraryServiceHandlerFromEndpoint is same as RegisterCredentialLibraryServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterCredentialLibraryServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterCredentialLibraryServiceHandler(ctx, mux, conn) +} + +// RegisterCredentialLibraryServiceHandler registers the http handlers for service CredentialLibraryService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterCredentialLibraryServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterCredentialLibraryServiceHandlerClient(ctx, mux, NewCredentialLibraryServiceClient(conn)) +} + +// RegisterCredentialLibraryServiceHandlerClient registers the http handlers for service CredentialLibraryService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "CredentialLibraryServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "CredentialLibraryServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "CredentialLibraryServiceClient" to call the correct interceptors. +func RegisterCredentialLibraryServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client CredentialLibraryServiceClient) error { + + mux.Handle("GET", pattern_CredentialLibraryService_GetCredentialLibrary_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/GetCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialLibraryService_GetCredentialLibrary_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_GetCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_GetCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialLibraryService_ListCredentialLibraries_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/ListCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialLibraryService_ListCredentialLibraries_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_ListCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialLibraryService_CreateCredentialLibrary_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/CreateCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialLibraryService_CreateCredentialLibrary_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_CreateCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_CreateCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialLibraryService_UpdateCredentialLibrary_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/UpdateCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialLibraryService_UpdateCredentialLibrary_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_UpdateCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, response_CredentialLibraryService_UpdateCredentialLibrary_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialLibraryService_DeleteCredentialLibrary_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialLibraryService/DeleteCredentialLibrary") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialLibraryService_DeleteCredentialLibrary_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialLibraryService_DeleteCredentialLibrary_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_CredentialLibraryService_GetCredentialLibrary_0 struct { + proto.Message +} + +func (m response_CredentialLibraryService_GetCredentialLibrary_0) XXX_ResponseBody() interface{} { + response := m.Message.(*GetCredentialLibraryResponse) + return response.Item +} + +type response_CredentialLibraryService_CreateCredentialLibrary_0 struct { + proto.Message +} + +func (m response_CredentialLibraryService_CreateCredentialLibrary_0) XXX_ResponseBody() interface{} { + response := m.Message.(*CreateCredentialLibraryResponse) + return response.Item +} + +type response_CredentialLibraryService_UpdateCredentialLibrary_0 struct { + proto.Message +} + +func (m response_CredentialLibraryService_UpdateCredentialLibrary_0) XXX_ResponseBody() interface{} { + response := m.Message.(*UpdateCredentialLibraryResponse) + return response.Item +} + +var ( + pattern_CredentialLibraryService_GetCredentialLibrary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-libraries", "id"}, "")) + + pattern_CredentialLibraryService_ListCredentialLibraries_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credential-libraries"}, "")) + + pattern_CredentialLibraryService_CreateCredentialLibrary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credential-libraries"}, "")) + + pattern_CredentialLibraryService_UpdateCredentialLibrary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-libraries", "id"}, "")) + + pattern_CredentialLibraryService_DeleteCredentialLibrary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-libraries", "id"}, "")) +) + +var ( + forward_CredentialLibraryService_GetCredentialLibrary_0 = runtime.ForwardResponseMessage + + forward_CredentialLibraryService_ListCredentialLibraries_0 = runtime.ForwardResponseMessage + + forward_CredentialLibraryService_CreateCredentialLibrary_0 = runtime.ForwardResponseMessage + + forward_CredentialLibraryService_UpdateCredentialLibrary_0 = runtime.ForwardResponseMessage + + forward_CredentialLibraryService_DeleteCredentialLibrary_0 = runtime.ForwardResponseMessage +) diff --git a/internal/gen/controller/api/services/credential_library_service_grpc.pb.go b/internal/gen/controller/api/services/credential_library_service_grpc.pb.go new file mode 100644 index 000000000..d0cff06c7 --- /dev/null +++ b/internal/gen/controller/api/services/credential_library_service_grpc.pb.go @@ -0,0 +1,288 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package services + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// CredentialLibraryServiceClient is the client API for CredentialLibraryService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CredentialLibraryServiceClient interface { + // GetCredentialLibrary returns a stored Credential Library if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredentialLibrary(ctx context.Context, in *GetCredentialLibraryRequest, opts ...grpc.CallOption) (*GetCredentialLibraryResponse, error) + // ListCredentialLibraries returns a list of stored Credential Libraries which are in the + // provided scope. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentialLibraries(ctx context.Context, in *ListCredentialLibrariesRequest, opts ...grpc.CallOption) (*ListCredentialLibrariesResponse, error) + // CreateCredentialLibrary creates and stores an Credential Library in boundary. The + // provided request must include the scope in which the Credential Library will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Library in the same scope, an error is returned. + CreateCredentialLibrary(ctx context.Context, in *CreateCredentialLibraryRequest, opts ...grpc.CallOption) (*CreateCredentialLibraryResponse, error) + // UpdateCredentialLibrary updates an existing Credential Library in boundary. The provided + // Credential Library 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Library id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Library in the parent scope. + UpdateCredentialLibrary(ctx context.Context, in *UpdateCredentialLibraryRequest, opts ...grpc.CallOption) (*UpdateCredentialLibraryResponse, error) + // DeleteCredentialLibrary removes an Credential Library from Boundary. If the Credential Library id + // is malformed or not provided an error is returned. + DeleteCredentialLibrary(ctx context.Context, in *DeleteCredentialLibraryRequest, opts ...grpc.CallOption) (*DeleteCredentialLibraryResponse, error) +} + +type credentialLibraryServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCredentialLibraryServiceClient(cc grpc.ClientConnInterface) CredentialLibraryServiceClient { + return &credentialLibraryServiceClient{cc} +} + +func (c *credentialLibraryServiceClient) GetCredentialLibrary(ctx context.Context, in *GetCredentialLibraryRequest, opts ...grpc.CallOption) (*GetCredentialLibraryResponse, error) { + out := new(GetCredentialLibraryResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialLibraryService/GetCredentialLibrary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialLibraryServiceClient) ListCredentialLibraries(ctx context.Context, in *ListCredentialLibrariesRequest, opts ...grpc.CallOption) (*ListCredentialLibrariesResponse, error) { + out := new(ListCredentialLibrariesResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialLibraryService/ListCredentialLibraries", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialLibraryServiceClient) CreateCredentialLibrary(ctx context.Context, in *CreateCredentialLibraryRequest, opts ...grpc.CallOption) (*CreateCredentialLibraryResponse, error) { + out := new(CreateCredentialLibraryResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialLibraryService/CreateCredentialLibrary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialLibraryServiceClient) UpdateCredentialLibrary(ctx context.Context, in *UpdateCredentialLibraryRequest, opts ...grpc.CallOption) (*UpdateCredentialLibraryResponse, error) { + out := new(UpdateCredentialLibraryResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialLibraryService/UpdateCredentialLibrary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialLibraryServiceClient) DeleteCredentialLibrary(ctx context.Context, in *DeleteCredentialLibraryRequest, opts ...grpc.CallOption) (*DeleteCredentialLibraryResponse, error) { + out := new(DeleteCredentialLibraryResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialLibraryService/DeleteCredentialLibrary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CredentialLibraryServiceServer is the server API for CredentialLibraryService service. +// All implementations must embed UnimplementedCredentialLibraryServiceServer +// for forward compatibility +type CredentialLibraryServiceServer interface { + // GetCredentialLibrary returns a stored Credential Library if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredentialLibrary(context.Context, *GetCredentialLibraryRequest) (*GetCredentialLibraryResponse, error) + // ListCredentialLibraries returns a list of stored Credential Libraries which are in the + // provided scope. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentialLibraries(context.Context, *ListCredentialLibrariesRequest) (*ListCredentialLibrariesResponse, error) + // CreateCredentialLibrary creates and stores an Credential Library in boundary. The + // provided request must include the scope in which the Credential Library will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Library in the same scope, an error is returned. + CreateCredentialLibrary(context.Context, *CreateCredentialLibraryRequest) (*CreateCredentialLibraryResponse, error) + // UpdateCredentialLibrary updates an existing Credential Library in boundary. The provided + // Credential Library 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Library id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Library in the parent scope. + UpdateCredentialLibrary(context.Context, *UpdateCredentialLibraryRequest) (*UpdateCredentialLibraryResponse, error) + // DeleteCredentialLibrary removes an Credential Library from Boundary. If the Credential Library id + // is malformed or not provided an error is returned. + DeleteCredentialLibrary(context.Context, *DeleteCredentialLibraryRequest) (*DeleteCredentialLibraryResponse, error) + mustEmbedUnimplementedCredentialLibraryServiceServer() +} + +// UnimplementedCredentialLibraryServiceServer must be embedded to have forward compatible implementations. +type UnimplementedCredentialLibraryServiceServer struct { +} + +func (UnimplementedCredentialLibraryServiceServer) GetCredentialLibrary(context.Context, *GetCredentialLibraryRequest) (*GetCredentialLibraryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCredentialLibrary not implemented") +} +func (UnimplementedCredentialLibraryServiceServer) ListCredentialLibraries(context.Context, *ListCredentialLibrariesRequest) (*ListCredentialLibrariesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCredentialLibraries not implemented") +} +func (UnimplementedCredentialLibraryServiceServer) CreateCredentialLibrary(context.Context, *CreateCredentialLibraryRequest) (*CreateCredentialLibraryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCredentialLibrary not implemented") +} +func (UnimplementedCredentialLibraryServiceServer) UpdateCredentialLibrary(context.Context, *UpdateCredentialLibraryRequest) (*UpdateCredentialLibraryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateCredentialLibrary not implemented") +} +func (UnimplementedCredentialLibraryServiceServer) DeleteCredentialLibrary(context.Context, *DeleteCredentialLibraryRequest) (*DeleteCredentialLibraryResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteCredentialLibrary not implemented") +} +func (UnimplementedCredentialLibraryServiceServer) mustEmbedUnimplementedCredentialLibraryServiceServer() { +} + +// UnsafeCredentialLibraryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CredentialLibraryServiceServer will +// result in compilation errors. +type UnsafeCredentialLibraryServiceServer interface { + mustEmbedUnimplementedCredentialLibraryServiceServer() +} + +func RegisterCredentialLibraryServiceServer(s grpc.ServiceRegistrar, srv CredentialLibraryServiceServer) { + s.RegisterService(&CredentialLibraryService_ServiceDesc, srv) +} + +func _CredentialLibraryService_GetCredentialLibrary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCredentialLibraryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialLibraryServiceServer).GetCredentialLibrary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialLibraryService/GetCredentialLibrary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialLibraryServiceServer).GetCredentialLibrary(ctx, req.(*GetCredentialLibraryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialLibraryService_ListCredentialLibraries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCredentialLibrariesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialLibraryServiceServer).ListCredentialLibraries(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialLibraryService/ListCredentialLibraries", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialLibraryServiceServer).ListCredentialLibraries(ctx, req.(*ListCredentialLibrariesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialLibraryService_CreateCredentialLibrary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCredentialLibraryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialLibraryServiceServer).CreateCredentialLibrary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialLibraryService/CreateCredentialLibrary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialLibraryServiceServer).CreateCredentialLibrary(ctx, req.(*CreateCredentialLibraryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialLibraryService_UpdateCredentialLibrary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateCredentialLibraryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialLibraryServiceServer).UpdateCredentialLibrary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialLibraryService/UpdateCredentialLibrary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialLibraryServiceServer).UpdateCredentialLibrary(ctx, req.(*UpdateCredentialLibraryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialLibraryService_DeleteCredentialLibrary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteCredentialLibraryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialLibraryServiceServer).DeleteCredentialLibrary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialLibraryService/DeleteCredentialLibrary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialLibraryServiceServer).DeleteCredentialLibrary(ctx, req.(*DeleteCredentialLibraryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CredentialLibraryService_ServiceDesc is the grpc.ServiceDesc for CredentialLibraryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CredentialLibraryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "controller.api.services.v1.CredentialLibraryService", + HandlerType: (*CredentialLibraryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCredentialLibrary", + Handler: _CredentialLibraryService_GetCredentialLibrary_Handler, + }, + { + MethodName: "ListCredentialLibraries", + Handler: _CredentialLibraryService_ListCredentialLibraries_Handler, + }, + { + MethodName: "CreateCredentialLibrary", + Handler: _CredentialLibraryService_CreateCredentialLibrary_Handler, + }, + { + MethodName: "UpdateCredentialLibrary", + Handler: _CredentialLibraryService_UpdateCredentialLibrary_Handler, + }, + { + MethodName: "DeleteCredentialLibrary", + Handler: _CredentialLibraryService_DeleteCredentialLibrary_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "controller/api/services/v1/credential_library_service.proto", +} diff --git a/internal/gen/controller/api/services/credential_store_service.pb.go b/internal/gen/controller/api/services/credential_store_service.pb.go new file mode 100644 index 000000000..421747a51 --- /dev/null +++ b/internal/gen/controller/api/services/credential_store_service.pb.go @@ -0,0 +1,886 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.15.8 +// source: controller/api/services/v1/credential_store_service.proto + +package services + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + credentialstores "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores" + _ "google.golang.org/genproto/googleapis/api/annotations" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + fieldmaskpb "google.golang.org/protobuf/types/known/fieldmaskpb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type GetCredentialStoreRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *GetCredentialStoreRequest) Reset() { + *x = GetCredentialStoreRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialStoreRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialStoreRequest) ProtoMessage() {} + +func (x *GetCredentialStoreRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[0] + 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 GetCredentialStoreRequest.ProtoReflect.Descriptor instead. +func (*GetCredentialStoreRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetCredentialStoreRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetCredentialStoreResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentialstores.CredentialStore `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *GetCredentialStoreResponse) Reset() { + *x = GetCredentialStoreResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialStoreResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialStoreResponse) ProtoMessage() {} + +func (x *GetCredentialStoreResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[1] + 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 GetCredentialStoreResponse.ProtoReflect.Descriptor instead. +func (*GetCredentialStoreResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCredentialStoreResponse) GetItem() *credentialstores.CredentialStore { + if x != nil { + return x.Item + } + return nil +} + +type ListCredentialStoresRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ScopeId string `protobuf:"bytes,1,opt,name=scope_id,proto3" json:"scope_id,omitempty"` + Recursive bool `protobuf:"varint,20,opt,name=recursive,proto3" json:"recursive,omitempty"` + Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty"` +} + +func (x *ListCredentialStoresRequest) Reset() { + *x = ListCredentialStoresRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialStoresRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialStoresRequest) ProtoMessage() {} + +func (x *ListCredentialStoresRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_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 ListCredentialStoresRequest.ProtoReflect.Descriptor instead. +func (*ListCredentialStoresRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListCredentialStoresRequest) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *ListCredentialStoresRequest) GetRecursive() bool { + if x != nil { + return x.Recursive + } + return false +} + +func (x *ListCredentialStoresRequest) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +type ListCredentialStoresResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*credentialstores.CredentialStore `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ListCredentialStoresResponse) Reset() { + *x = ListCredentialStoresResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialStoresResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialStoresResponse) ProtoMessage() {} + +func (x *ListCredentialStoresResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[3] + 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 ListCredentialStoresResponse.ProtoReflect.Descriptor instead. +func (*ListCredentialStoresResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ListCredentialStoresResponse) GetItems() []*credentialstores.CredentialStore { + if x != nil { + return x.Items + } + return nil +} + +type CreateCredentialStoreRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentialstores.CredentialStore `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialStoreRequest) Reset() { + *x = CreateCredentialStoreRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialStoreRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialStoreRequest) ProtoMessage() {} + +func (x *CreateCredentialStoreRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[4] + 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 CreateCredentialStoreRequest.ProtoReflect.Descriptor instead. +func (*CreateCredentialStoreRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateCredentialStoreRequest) GetItem() *credentialstores.CredentialStore { + if x != nil { + return x.Item + } + return nil +} + +type CreateCredentialStoreResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty"` + Item *credentialstores.CredentialStore `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialStoreResponse) Reset() { + *x = CreateCredentialStoreResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialStoreResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialStoreResponse) ProtoMessage() {} + +func (x *CreateCredentialStoreResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[5] + 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 CreateCredentialStoreResponse.ProtoReflect.Descriptor instead. +func (*CreateCredentialStoreResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateCredentialStoreResponse) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *CreateCredentialStoreResponse) GetItem() *credentialstores.CredentialStore { + if x != nil { + return x.Item + } + return nil +} + +type UpdateCredentialStoreRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Item *credentialstores.CredentialStore `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` + UpdateMask *fieldmaskpb.FieldMask `protobuf:"bytes,3,opt,name=update_mask,proto3" json:"update_mask,omitempty"` +} + +func (x *UpdateCredentialStoreRequest) Reset() { + *x = UpdateCredentialStoreRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialStoreRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialStoreRequest) ProtoMessage() {} + +func (x *UpdateCredentialStoreRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_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 UpdateCredentialStoreRequest.ProtoReflect.Descriptor instead. +func (*UpdateCredentialStoreRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateCredentialStoreRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateCredentialStoreRequest) GetItem() *credentialstores.CredentialStore { + if x != nil { + return x.Item + } + return nil +} + +func (x *UpdateCredentialStoreRequest) GetUpdateMask() *fieldmaskpb.FieldMask { + if x != nil { + return x.UpdateMask + } + return nil +} + +type UpdateCredentialStoreResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentialstores.CredentialStore `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *UpdateCredentialStoreResponse) Reset() { + *x = UpdateCredentialStoreResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialStoreResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialStoreResponse) ProtoMessage() {} + +func (x *UpdateCredentialStoreResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_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 UpdateCredentialStoreResponse.ProtoReflect.Descriptor instead. +func (*UpdateCredentialStoreResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateCredentialStoreResponse) GetItem() *credentialstores.CredentialStore { + if x != nil { + return x.Item + } + return nil +} + +type DeleteCredentialStoreRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (x *DeleteCredentialStoreRequest) Reset() { + *x = DeleteCredentialStoreRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialStoreRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialStoreRequest) ProtoMessage() {} + +func (x *DeleteCredentialStoreRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[8] + 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 DeleteCredentialStoreRequest.ProtoReflect.Descriptor instead. +func (*DeleteCredentialStoreRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteCredentialStoreRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeleteCredentialStoreResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteCredentialStoreResponse) Reset() { + *x = DeleteCredentialStoreResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialStoreResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialStoreResponse) ProtoMessage() {} + +func (x *DeleteCredentialStoreResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_store_service_proto_msgTypes[9] + 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 DeleteCredentialStoreResponse.ProtoReflect.Descriptor instead. +func (*DeleteCredentialStoreResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP(), []int{9} +} + +var File_controller_api_services_v1_credential_store_service_proto protoreflect.FileDescriptor + +var file_controller_api_services_v1_credential_store_service_proto_rawDesc = []byte{ + 0x0a, 0x39, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, + 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1a, 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, 0x1a, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x2d, + 0x67, 0x65, 0x6e, 0x2d, 0x6f, 0x70, 0x65, 0x6e, 0x61, 0x70, 0x69, 0x76, 0x32, 0x2f, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, + 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x5f, 0x6d, 0x61, 0x73, + 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x43, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2b, 0x0a, 0x19, + 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x6f, 0x0a, 0x1a, 0x47, 0x65, 0x74, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x6f, 0x0a, 0x1b, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, 0x69, + 0x76, 0x65, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x72, 0x65, 0x63, 0x75, 0x72, 0x73, + 0x69, 0x76, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0x73, 0x0a, 0x1c, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x05, 0x69, + 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x71, 0x0a, 0x1c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, + 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x22, 0x84, 0x01, 0x0a, 0x1d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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, 0x51, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xbf, 0x01, 0x0a, 0x1c, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x51, 0x0a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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, 0x72, 0x0a, 0x1d, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x51, 0x0a, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3d, 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, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x22, 0x2e, 0x0a, 0x1c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, + 0x22, 0x1f, 0x0a, 0x1d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x32, 0xc9, 0x08, 0x0a, 0x16, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xd1, 0x01, 0x0a, + 0x12, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x12, 0x35, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x22, 0x4c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x22, 0x12, 0x1a, 0x2f, 0x76, 0x31, 0x2f, + 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x21, 0x12, + 0x1f, 0x47, 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, + 0x12, 0xc9, 0x01, 0x0a, 0x14, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x12, 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, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x38, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x17, 0x12, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x92, 0x41, 0x1e, 0x12, 0x1c, + 0x4c, 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2e, 0x12, 0xde, 0x01, 0x0a, + 0x15, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x39, 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, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x50, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x23, 0x22, 0x15, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x24, 0x12, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x12, 0xdc, 0x01, + 0x0a, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x28, 0x32, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x1d, 0x12, + 0x1b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x12, 0xce, 0x01, 0x0a, + 0x15, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x38, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x39, 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, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1c, 0x2a, 0x1a, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x2d, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, + 0x41, 0x1b, 0x12, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x6f, 0x72, 0x65, 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 ( + file_controller_api_services_v1_credential_store_service_proto_rawDescOnce sync.Once + file_controller_api_services_v1_credential_store_service_proto_rawDescData = file_controller_api_services_v1_credential_store_service_proto_rawDesc +) + +func file_controller_api_services_v1_credential_store_service_proto_rawDescGZIP() []byte { + file_controller_api_services_v1_credential_store_service_proto_rawDescOnce.Do(func() { + file_controller_api_services_v1_credential_store_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_credential_store_service_proto_rawDescData) + }) + return file_controller_api_services_v1_credential_store_service_proto_rawDescData +} + +var file_controller_api_services_v1_credential_store_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_controller_api_services_v1_credential_store_service_proto_goTypes = []interface{}{ + (*GetCredentialStoreRequest)(nil), // 0: controller.api.services.v1.GetCredentialStoreRequest + (*GetCredentialStoreResponse)(nil), // 1: controller.api.services.v1.GetCredentialStoreResponse + (*ListCredentialStoresRequest)(nil), // 2: controller.api.services.v1.ListCredentialStoresRequest + (*ListCredentialStoresResponse)(nil), // 3: controller.api.services.v1.ListCredentialStoresResponse + (*CreateCredentialStoreRequest)(nil), // 4: controller.api.services.v1.CreateCredentialStoreRequest + (*CreateCredentialStoreResponse)(nil), // 5: controller.api.services.v1.CreateCredentialStoreResponse + (*UpdateCredentialStoreRequest)(nil), // 6: controller.api.services.v1.UpdateCredentialStoreRequest + (*UpdateCredentialStoreResponse)(nil), // 7: controller.api.services.v1.UpdateCredentialStoreResponse + (*DeleteCredentialStoreRequest)(nil), // 8: controller.api.services.v1.DeleteCredentialStoreRequest + (*DeleteCredentialStoreResponse)(nil), // 9: controller.api.services.v1.DeleteCredentialStoreResponse + (*credentialstores.CredentialStore)(nil), // 10: controller.api.resources.credentialstores.v1.CredentialStore + (*fieldmaskpb.FieldMask)(nil), // 11: google.protobuf.FieldMask +} +var file_controller_api_services_v1_credential_store_service_proto_depIdxs = []int32{ + 10, // 0: controller.api.services.v1.GetCredentialStoreResponse.item:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 10, // 1: controller.api.services.v1.ListCredentialStoresResponse.items:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 10, // 2: controller.api.services.v1.CreateCredentialStoreRequest.item:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 10, // 3: controller.api.services.v1.CreateCredentialStoreResponse.item:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 10, // 4: controller.api.services.v1.UpdateCredentialStoreRequest.item:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 11, // 5: controller.api.services.v1.UpdateCredentialStoreRequest.update_mask:type_name -> google.protobuf.FieldMask + 10, // 6: controller.api.services.v1.UpdateCredentialStoreResponse.item:type_name -> controller.api.resources.credentialstores.v1.CredentialStore + 0, // 7: controller.api.services.v1.CredentialStoreService.GetCredentialStore:input_type -> controller.api.services.v1.GetCredentialStoreRequest + 2, // 8: controller.api.services.v1.CredentialStoreService.ListCredentialStores:input_type -> controller.api.services.v1.ListCredentialStoresRequest + 4, // 9: controller.api.services.v1.CredentialStoreService.CreateCredentialStore:input_type -> controller.api.services.v1.CreateCredentialStoreRequest + 6, // 10: controller.api.services.v1.CredentialStoreService.UpdateCredentialStore:input_type -> controller.api.services.v1.UpdateCredentialStoreRequest + 8, // 11: controller.api.services.v1.CredentialStoreService.DeleteCredentialStore:input_type -> controller.api.services.v1.DeleteCredentialStoreRequest + 1, // 12: controller.api.services.v1.CredentialStoreService.GetCredentialStore:output_type -> controller.api.services.v1.GetCredentialStoreResponse + 3, // 13: controller.api.services.v1.CredentialStoreService.ListCredentialStores:output_type -> controller.api.services.v1.ListCredentialStoresResponse + 5, // 14: controller.api.services.v1.CredentialStoreService.CreateCredentialStore:output_type -> controller.api.services.v1.CreateCredentialStoreResponse + 7, // 15: controller.api.services.v1.CredentialStoreService.UpdateCredentialStore:output_type -> controller.api.services.v1.UpdateCredentialStoreResponse + 9, // 16: controller.api.services.v1.CredentialStoreService.DeleteCredentialStore:output_type -> controller.api.services.v1.DeleteCredentialStoreResponse + 12, // [12:17] is the sub-list for method output_type + 7, // [7:12] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_controller_api_services_v1_credential_store_service_proto_init() } +func file_controller_api_services_v1_credential_store_service_proto_init() { + if File_controller_api_services_v1_credential_store_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_services_v1_credential_store_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialStoreRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialStoreResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialStoresRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialStoresResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialStoreRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialStoreResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialStoreRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialStoreResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialStoreRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_credential_store_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialStoreResponse); 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{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_services_v1_credential_store_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_controller_api_services_v1_credential_store_service_proto_goTypes, + DependencyIndexes: file_controller_api_services_v1_credential_store_service_proto_depIdxs, + MessageInfos: file_controller_api_services_v1_credential_store_service_proto_msgTypes, + }.Build() + File_controller_api_services_v1_credential_store_service_proto = out.File + file_controller_api_services_v1_credential_store_service_proto_rawDesc = nil + file_controller_api_services_v1_credential_store_service_proto_goTypes = nil + file_controller_api_services_v1_credential_store_service_proto_depIdxs = nil +} diff --git a/internal/gen/controller/api/services/credential_store_service.pb.gw.go b/internal/gen/controller/api/services/credential_store_service.pb.gw.go new file mode 100644 index 000000000..d7a5d0fd1 --- /dev/null +++ b/internal/gen/controller/api/services/credential_store_service.pb.gw.go @@ -0,0 +1,622 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: controller/api/services/v1/credential_store_service.proto + +/* +Package services is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package services + +import ( + "context" + "io" + "net/http" + + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/grpc-ecosystem/grpc-gateway/v2/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = metadata.Join + +func request_CredentialStoreService_GetCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialStoreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialStoreRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.GetCredentialStore(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialStoreService_GetCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialStoreServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialStoreRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.GetCredentialStore(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialStoreService_ListCredentialStores_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_CredentialStoreService_ListCredentialStores_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialStoreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialStoresRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialStoreService_ListCredentialStores_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListCredentialStores(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialStoreService_ListCredentialStores_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialStoreServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialStoresRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialStoreService_ListCredentialStores_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListCredentialStores(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialStoreService_CreateCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialStoreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialStoreRequest + 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.CreateCredentialStore(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialStoreService_CreateCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialStoreServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialStoreRequest + 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.CreateCredentialStore(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialStoreService_UpdateCredentialStore_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}} +) + +func request_CredentialStoreService_UpdateCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialStoreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialStoreRequest + 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) + } + if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 { + if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Item); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } else { + protoReq.UpdateMask = fieldMask + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialStoreService_UpdateCredentialStore_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateCredentialStore(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialStoreService_UpdateCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialStoreServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialStoreRequest + 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) + } + if protoReq.UpdateMask == nil || len(protoReq.UpdateMask.GetPaths()) == 0 { + if fieldMask, err := runtime.FieldMaskFromRequestBody(newReader(), protoReq.Item); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } else { + protoReq.UpdateMask = fieldMask + } + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CredentialStoreService_UpdateCredentialStore_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateCredentialStore(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialStoreService_DeleteCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialStoreServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialStoreRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.DeleteCredentialStore(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialStoreService_DeleteCredentialStore_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialStoreServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialStoreRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.DeleteCredentialStore(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterCredentialStoreServiceHandlerServer registers the http handlers for service CredentialStoreService to "mux". +// UnaryRPC :call CredentialStoreServiceServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterCredentialStoreServiceHandlerFromEndpoint instead. +func RegisterCredentialStoreServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server CredentialStoreServiceServer) error { + + mux.Handle("GET", pattern_CredentialStoreService_GetCredentialStore_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/GetCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialStoreService_GetCredentialStore_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_GetCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_GetCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialStoreService_ListCredentialStores_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/ListCredentialStores") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialStoreService_ListCredentialStores_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_ListCredentialStores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialStoreService_CreateCredentialStore_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/CreateCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialStoreService_CreateCredentialStore_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_CreateCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_CreateCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialStoreService_UpdateCredentialStore_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/UpdateCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialStoreService_UpdateCredentialStore_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_UpdateCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_UpdateCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialStoreService_DeleteCredentialStore_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/DeleteCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialStoreService_DeleteCredentialStore_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_DeleteCredentialStore_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterCredentialStoreServiceHandlerFromEndpoint is same as RegisterCredentialStoreServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterCredentialStoreServiceHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterCredentialStoreServiceHandler(ctx, mux, conn) +} + +// RegisterCredentialStoreServiceHandler registers the http handlers for service CredentialStoreService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterCredentialStoreServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterCredentialStoreServiceHandlerClient(ctx, mux, NewCredentialStoreServiceClient(conn)) +} + +// RegisterCredentialStoreServiceHandlerClient registers the http handlers for service CredentialStoreService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "CredentialStoreServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "CredentialStoreServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "CredentialStoreServiceClient" to call the correct interceptors. +func RegisterCredentialStoreServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client CredentialStoreServiceClient) error { + + mux.Handle("GET", pattern_CredentialStoreService_GetCredentialStore_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/GetCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialStoreService_GetCredentialStore_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_GetCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_GetCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialStoreService_ListCredentialStores_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/ListCredentialStores") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialStoreService_ListCredentialStores_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_ListCredentialStores_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialStoreService_CreateCredentialStore_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/CreateCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialStoreService_CreateCredentialStore_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_CreateCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_CreateCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialStoreService_UpdateCredentialStore_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/UpdateCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialStoreService_UpdateCredentialStore_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_UpdateCredentialStore_0(ctx, mux, outboundMarshaler, w, req, response_CredentialStoreService_UpdateCredentialStore_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialStoreService_DeleteCredentialStore_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialStoreService/DeleteCredentialStore") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialStoreService_DeleteCredentialStore_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialStoreService_DeleteCredentialStore_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_CredentialStoreService_GetCredentialStore_0 struct { + proto.Message +} + +func (m response_CredentialStoreService_GetCredentialStore_0) XXX_ResponseBody() interface{} { + response := m.Message.(*GetCredentialStoreResponse) + return response.Item +} + +type response_CredentialStoreService_CreateCredentialStore_0 struct { + proto.Message +} + +func (m response_CredentialStoreService_CreateCredentialStore_0) XXX_ResponseBody() interface{} { + response := m.Message.(*CreateCredentialStoreResponse) + return response.Item +} + +type response_CredentialStoreService_UpdateCredentialStore_0 struct { + proto.Message +} + +func (m response_CredentialStoreService_UpdateCredentialStore_0) XXX_ResponseBody() interface{} { + response := m.Message.(*UpdateCredentialStoreResponse) + return response.Item +} + +var ( + pattern_CredentialStoreService_GetCredentialStore_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-stores", "id"}, "")) + + pattern_CredentialStoreService_ListCredentialStores_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credential-stores"}, "")) + + pattern_CredentialStoreService_CreateCredentialStore_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credential-stores"}, "")) + + pattern_CredentialStoreService_UpdateCredentialStore_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-stores", "id"}, "")) + + pattern_CredentialStoreService_DeleteCredentialStore_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credential-stores", "id"}, "")) +) + +var ( + forward_CredentialStoreService_GetCredentialStore_0 = runtime.ForwardResponseMessage + + forward_CredentialStoreService_ListCredentialStores_0 = runtime.ForwardResponseMessage + + forward_CredentialStoreService_CreateCredentialStore_0 = runtime.ForwardResponseMessage + + forward_CredentialStoreService_UpdateCredentialStore_0 = runtime.ForwardResponseMessage + + forward_CredentialStoreService_DeleteCredentialStore_0 = runtime.ForwardResponseMessage +) diff --git a/internal/gen/controller/api/services/credential_store_service_grpc.pb.go b/internal/gen/controller/api/services/credential_store_service_grpc.pb.go new file mode 100644 index 000000000..52e1b6abf --- /dev/null +++ b/internal/gen/controller/api/services/credential_store_service_grpc.pb.go @@ -0,0 +1,288 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package services + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// CredentialStoreServiceClient is the client API for CredentialStoreService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type CredentialStoreServiceClient interface { + // GetCredentialStore returns a stored Credential Store if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredentialStore(ctx context.Context, in *GetCredentialStoreRequest, opts ...grpc.CallOption) (*GetCredentialStoreResponse, error) + // ListCredentialStores returns a list of stored Credential Stores which are in the + // provided scope. The request must include the scope id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentialStores(ctx context.Context, in *ListCredentialStoresRequest, opts ...grpc.CallOption) (*ListCredentialStoresResponse, error) + // CreateCredentialStore creates and stores an Credential Store in boundary. The + // provided request must include the scope in which the Credential Store will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Store in the same scope, an error is returned. + CreateCredentialStore(ctx context.Context, in *CreateCredentialStoreRequest, opts ...grpc.CallOption) (*CreateCredentialStoreResponse, error) + // UpdateCredentialStore updates an existing Credential Store in boundary. The provided + // Credential Store 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Store id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Store in the parent scope. + UpdateCredentialStore(ctx context.Context, in *UpdateCredentialStoreRequest, opts ...grpc.CallOption) (*UpdateCredentialStoreResponse, error) + // DeleteCredentialStore removes a Credential Store from Boundary. If the Credential Store id + // is malformed or not provided an error is returned. + DeleteCredentialStore(ctx context.Context, in *DeleteCredentialStoreRequest, opts ...grpc.CallOption) (*DeleteCredentialStoreResponse, error) +} + +type credentialStoreServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCredentialStoreServiceClient(cc grpc.ClientConnInterface) CredentialStoreServiceClient { + return &credentialStoreServiceClient{cc} +} + +func (c *credentialStoreServiceClient) GetCredentialStore(ctx context.Context, in *GetCredentialStoreRequest, opts ...grpc.CallOption) (*GetCredentialStoreResponse, error) { + out := new(GetCredentialStoreResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialStoreService/GetCredentialStore", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialStoreServiceClient) ListCredentialStores(ctx context.Context, in *ListCredentialStoresRequest, opts ...grpc.CallOption) (*ListCredentialStoresResponse, error) { + out := new(ListCredentialStoresResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialStoreService/ListCredentialStores", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialStoreServiceClient) CreateCredentialStore(ctx context.Context, in *CreateCredentialStoreRequest, opts ...grpc.CallOption) (*CreateCredentialStoreResponse, error) { + out := new(CreateCredentialStoreResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialStoreService/CreateCredentialStore", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialStoreServiceClient) UpdateCredentialStore(ctx context.Context, in *UpdateCredentialStoreRequest, opts ...grpc.CallOption) (*UpdateCredentialStoreResponse, error) { + out := new(UpdateCredentialStoreResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialStoreService/UpdateCredentialStore", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialStoreServiceClient) DeleteCredentialStore(ctx context.Context, in *DeleteCredentialStoreRequest, opts ...grpc.CallOption) (*DeleteCredentialStoreResponse, error) { + out := new(DeleteCredentialStoreResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialStoreService/DeleteCredentialStore", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CredentialStoreServiceServer is the server API for CredentialStoreService service. +// All implementations must embed UnimplementedCredentialStoreServiceServer +// for forward compatibility +type CredentialStoreServiceServer interface { + // GetCredentialStore returns a stored Credential Store if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredentialStore(context.Context, *GetCredentialStoreRequest) (*GetCredentialStoreResponse, error) + // ListCredentialStores returns a list of stored Credential Stores which are in the + // provided scope. The request must include the scope id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentialStores(context.Context, *ListCredentialStoresRequest) (*ListCredentialStoresResponse, error) + // CreateCredentialStore creates and stores an Credential Store in boundary. The + // provided request must include the scope in which the Credential Store will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Store in the same scope, an error is returned. + CreateCredentialStore(context.Context, *CreateCredentialStoreRequest) (*CreateCredentialStoreResponse, error) + // UpdateCredentialStore updates an existing Credential Store in boundary. The provided + // Credential Store 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Store id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Store in the parent scope. + UpdateCredentialStore(context.Context, *UpdateCredentialStoreRequest) (*UpdateCredentialStoreResponse, error) + // DeleteCredentialStore removes a Credential Store from Boundary. If the Credential Store id + // is malformed or not provided an error is returned. + DeleteCredentialStore(context.Context, *DeleteCredentialStoreRequest) (*DeleteCredentialStoreResponse, error) + mustEmbedUnimplementedCredentialStoreServiceServer() +} + +// UnimplementedCredentialStoreServiceServer must be embedded to have forward compatible implementations. +type UnimplementedCredentialStoreServiceServer struct { +} + +func (UnimplementedCredentialStoreServiceServer) GetCredentialStore(context.Context, *GetCredentialStoreRequest) (*GetCredentialStoreResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCredentialStore not implemented") +} +func (UnimplementedCredentialStoreServiceServer) ListCredentialStores(context.Context, *ListCredentialStoresRequest) (*ListCredentialStoresResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCredentialStores not implemented") +} +func (UnimplementedCredentialStoreServiceServer) CreateCredentialStore(context.Context, *CreateCredentialStoreRequest) (*CreateCredentialStoreResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCredentialStore not implemented") +} +func (UnimplementedCredentialStoreServiceServer) UpdateCredentialStore(context.Context, *UpdateCredentialStoreRequest) (*UpdateCredentialStoreResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateCredentialStore not implemented") +} +func (UnimplementedCredentialStoreServiceServer) DeleteCredentialStore(context.Context, *DeleteCredentialStoreRequest) (*DeleteCredentialStoreResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteCredentialStore not implemented") +} +func (UnimplementedCredentialStoreServiceServer) mustEmbedUnimplementedCredentialStoreServiceServer() { +} + +// UnsafeCredentialStoreServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CredentialStoreServiceServer will +// result in compilation errors. +type UnsafeCredentialStoreServiceServer interface { + mustEmbedUnimplementedCredentialStoreServiceServer() +} + +func RegisterCredentialStoreServiceServer(s grpc.ServiceRegistrar, srv CredentialStoreServiceServer) { + s.RegisterService(&CredentialStoreService_ServiceDesc, srv) +} + +func _CredentialStoreService_GetCredentialStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCredentialStoreRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialStoreServiceServer).GetCredentialStore(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialStoreService/GetCredentialStore", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialStoreServiceServer).GetCredentialStore(ctx, req.(*GetCredentialStoreRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialStoreService_ListCredentialStores_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCredentialStoresRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialStoreServiceServer).ListCredentialStores(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialStoreService/ListCredentialStores", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialStoreServiceServer).ListCredentialStores(ctx, req.(*ListCredentialStoresRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialStoreService_CreateCredentialStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCredentialStoreRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialStoreServiceServer).CreateCredentialStore(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialStoreService/CreateCredentialStore", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialStoreServiceServer).CreateCredentialStore(ctx, req.(*CreateCredentialStoreRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialStoreService_UpdateCredentialStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateCredentialStoreRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialStoreServiceServer).UpdateCredentialStore(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialStoreService/UpdateCredentialStore", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialStoreServiceServer).UpdateCredentialStore(ctx, req.(*UpdateCredentialStoreRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialStoreService_DeleteCredentialStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteCredentialStoreRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialStoreServiceServer).DeleteCredentialStore(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialStoreService/DeleteCredentialStore", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialStoreServiceServer).DeleteCredentialStore(ctx, req.(*DeleteCredentialStoreRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CredentialStoreService_ServiceDesc is the grpc.ServiceDesc for CredentialStoreService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CredentialStoreService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "controller.api.services.v1.CredentialStoreService", + HandlerType: (*CredentialStoreServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCredentialStore", + Handler: _CredentialStoreService_GetCredentialStore_Handler, + }, + { + MethodName: "ListCredentialStores", + Handler: _CredentialStoreService_ListCredentialStores_Handler, + }, + { + MethodName: "CreateCredentialStore", + Handler: _CredentialStoreService_CreateCredentialStore_Handler, + }, + { + MethodName: "UpdateCredentialStore", + Handler: _CredentialStoreService_UpdateCredentialStore_Handler, + }, + { + MethodName: "DeleteCredentialStore", + Handler: _CredentialStoreService_DeleteCredentialStore_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "controller/api/services/v1/credential_store_service.proto", +} diff --git a/internal/gen/controller/api/services/target_service.pb.go b/internal/gen/controller/api/services/target_service.pb.go index adb174b78..99638bb32 100644 --- a/internal/gen/controller/api/services/target_service.pb.go +++ b/internal/gen/controller/api/services/target_service.pb.go @@ -861,6 +861,423 @@ func (x *RemoveTargetHostSetsResponse) GetItem() *targets.Target { return nil } +type CredentialLibrary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The id of the credential library. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The purpose of the credential library for this target. Currently this can + // only be "application" or left empty (which defaults the value to + // "application"). + Purpose string `protobuf:"bytes,2,opt,name=purpose,proto3" json:"purpose,omitempty"` +} + +func (x *CredentialLibrary) Reset() { + *x = CredentialLibrary{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialLibrary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialLibrary) ProtoMessage() {} + +func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[16] + 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 CredentialLibrary.ProtoReflect.Descriptor instead. +func (*CredentialLibrary) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{16} +} + +func (x *CredentialLibrary) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *CredentialLibrary) GetPurpose() string { + if x != nil { + return x.Purpose + } + return "" +} + +// The ids from both credential_library_ids and credential_libraries are used. +// When using credential_library_ids the default value "application" is used +// for the purpose. +type AddTargetCredentialLibrariesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + CredentialLibraryIds []string `protobuf:"bytes,3,rep,name=credential_library_ids,proto3" json:"credential_library_ids,omitempty"` + CredentialLibraries []*CredentialLibrary `protobuf:"bytes,4,rep,name=credential_libraries,proto3" json:"credential_libraries,omitempty"` +} + +func (x *AddTargetCredentialLibrariesRequest) Reset() { + *x = AddTargetCredentialLibrariesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddTargetCredentialLibrariesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddTargetCredentialLibrariesRequest) ProtoMessage() {} + +func (x *AddTargetCredentialLibrariesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[17] + 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 AddTargetCredentialLibrariesRequest.ProtoReflect.Descriptor instead. +func (*AddTargetCredentialLibrariesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{17} +} + +func (x *AddTargetCredentialLibrariesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *AddTargetCredentialLibrariesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *AddTargetCredentialLibrariesRequest) GetCredentialLibraryIds() []string { + if x != nil { + return x.CredentialLibraryIds + } + return nil +} + +func (x *AddTargetCredentialLibrariesRequest) GetCredentialLibraries() []*CredentialLibrary { + if x != nil { + return x.CredentialLibraries + } + return nil +} + +type AddTargetCredentialLibrariesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *targets.Target `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *AddTargetCredentialLibrariesResponse) Reset() { + *x = AddTargetCredentialLibrariesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AddTargetCredentialLibrariesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AddTargetCredentialLibrariesResponse) ProtoMessage() {} + +func (x *AddTargetCredentialLibrariesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[18] + 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 AddTargetCredentialLibrariesResponse.ProtoReflect.Descriptor instead. +func (*AddTargetCredentialLibrariesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{18} +} + +func (x *AddTargetCredentialLibrariesResponse) GetItem() *targets.Target { + if x != nil { + return x.Item + } + return nil +} + +// The ids from both credential_library_ids and credential_libraries are used. +// When using credential_library_ids the default value "application" is used +// for the purpose. +type SetTargetCredentialLibrariesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + CredentialLibraryIds []string `protobuf:"bytes,3,rep,name=credential_library_ids,proto3" json:"credential_library_ids,omitempty"` + CredentialLibraries []*CredentialLibrary `protobuf:"bytes,4,rep,name=credential_libraries,proto3" json:"credential_libraries,omitempty"` +} + +func (x *SetTargetCredentialLibrariesRequest) Reset() { + *x = SetTargetCredentialLibrariesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetTargetCredentialLibrariesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTargetCredentialLibrariesRequest) ProtoMessage() {} + +func (x *SetTargetCredentialLibrariesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[19] + 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 SetTargetCredentialLibrariesRequest.ProtoReflect.Descriptor instead. +func (*SetTargetCredentialLibrariesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{19} +} + +func (x *SetTargetCredentialLibrariesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *SetTargetCredentialLibrariesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *SetTargetCredentialLibrariesRequest) GetCredentialLibraryIds() []string { + if x != nil { + return x.CredentialLibraryIds + } + return nil +} + +func (x *SetTargetCredentialLibrariesRequest) GetCredentialLibraries() []*CredentialLibrary { + if x != nil { + return x.CredentialLibraries + } + return nil +} + +type SetTargetCredentialLibrariesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *targets.Target `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *SetTargetCredentialLibrariesResponse) Reset() { + *x = SetTargetCredentialLibrariesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SetTargetCredentialLibrariesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SetTargetCredentialLibrariesResponse) ProtoMessage() {} + +func (x *SetTargetCredentialLibrariesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[20] + 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 SetTargetCredentialLibrariesResponse.ProtoReflect.Descriptor instead. +func (*SetTargetCredentialLibrariesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{20} +} + +func (x *SetTargetCredentialLibrariesResponse) GetItem() *targets.Target { + if x != nil { + return x.Item + } + return nil +} + +type RemoveTargetCredentialLibrariesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + Version uint32 `protobuf:"varint,2,opt,name=version,proto3" json:"version,omitempty"` + CredentialLibraryIds []string `protobuf:"bytes,3,rep,name=credential_library_ids,proto3" json:"credential_library_ids,omitempty"` +} + +func (x *RemoveTargetCredentialLibrariesRequest) Reset() { + *x = RemoveTargetCredentialLibrariesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[21] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveTargetCredentialLibrariesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveTargetCredentialLibrariesRequest) ProtoMessage() {} + +func (x *RemoveTargetCredentialLibrariesRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[21] + 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 RemoveTargetCredentialLibrariesRequest.ProtoReflect.Descriptor instead. +func (*RemoveTargetCredentialLibrariesRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{21} +} + +func (x *RemoveTargetCredentialLibrariesRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RemoveTargetCredentialLibrariesRequest) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *RemoveTargetCredentialLibrariesRequest) GetCredentialLibraryIds() []string { + if x != nil { + return x.CredentialLibraryIds + } + return nil +} + +type RemoveTargetCredentialLibrariesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *targets.Target `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *RemoveTargetCredentialLibrariesResponse) Reset() { + *x = RemoveTargetCredentialLibrariesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[22] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RemoveTargetCredentialLibrariesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RemoveTargetCredentialLibrariesResponse) ProtoMessage() {} + +func (x *RemoveTargetCredentialLibrariesResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[22] + 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 RemoveTargetCredentialLibrariesResponse.ProtoReflect.Descriptor instead. +func (*RemoveTargetCredentialLibrariesResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{22} +} + +func (x *RemoveTargetCredentialLibrariesResponse) GetItem() *targets.Target { + if x != nil { + return x.Item + } + return nil +} + type AuthorizeSessionRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -881,7 +1298,7 @@ type AuthorizeSessionRequest struct { func (x *AuthorizeSessionRequest) Reset() { *x = AuthorizeSessionRequest{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_target_service_proto_msgTypes[16] + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -894,7 +1311,7 @@ func (x *AuthorizeSessionRequest) String() string { func (*AuthorizeSessionRequest) ProtoMessage() {} func (x *AuthorizeSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_target_service_proto_msgTypes[16] + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -907,7 +1324,7 @@ func (x *AuthorizeSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthorizeSessionRequest.ProtoReflect.Descriptor instead. func (*AuthorizeSessionRequest) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{16} + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{23} } func (x *AuthorizeSessionRequest) GetId() string { @@ -956,7 +1373,7 @@ type AuthorizeSessionResponse struct { func (x *AuthorizeSessionResponse) Reset() { *x = AuthorizeSessionResponse{} if protoimpl.UnsafeEnabled { - mi := &file_controller_api_services_v1_target_service_proto_msgTypes[17] + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -969,7 +1386,7 @@ func (x *AuthorizeSessionResponse) String() string { func (*AuthorizeSessionResponse) ProtoMessage() {} func (x *AuthorizeSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_services_v1_target_service_proto_msgTypes[17] + mi := &file_controller_api_services_v1_target_service_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -982,7 +1399,7 @@ func (x *AuthorizeSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthorizeSessionResponse.ProtoReflect.Descriptor instead. func (*AuthorizeSessionResponse) Descriptor() ([]byte, []int) { - return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{17} + return file_controller_api_services_v1_target_service_proto_rawDescGZIP(), []int{24} } func (x *AuthorizeSessionResponse) GetItem() *targets.SessionAuthorization { @@ -1099,137 +1516,251 @@ var file_controller_api_services_v1_target_service_proto_rawDesc = []byte{ 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, 0x01, 0x0a, 0x17, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, - 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x63, 0x6f, 0x70, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x22, 0x69, - 0x0a, 0x18, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0xc4, 0x0d, 0x0a, 0x0d, 0x54, 0x61, - 0x72, 0x67, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xa2, 0x01, 0x0a, 0x09, - 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x3d, 0x0a, 0x11, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x22, 0xea, 0x01, 0x0a, 0x23, 0x41, 0x64, + 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 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, 0x36, 0x0a, 0x16, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, + 0x69, 0x64, 0x73, 0x12, 0x61, 0x0a, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 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, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x52, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0x67, 0x0a, 0x24, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x69, 0x65, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, + 0xea, 0x01, 0x0a, 0x23, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 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, 0x36, 0x0a, 0x16, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x16, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x12, 0x61, 0x0a, 0x14, 0x63, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, + 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x18, 0x12, 0x10, - 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, - 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, 0x65, 0x74, 0x73, 0x20, - 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, - 0x12, 0x9a, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x92, 0x41, 0x14, 0x12, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x73, - 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x12, 0xaf, 0x01, - 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x3c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x0b, 0x2f, 0x76, 0x31, 0x2f, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, - 0x65, 0x6d, 0x92, 0x41, 0x1a, 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, - 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, - 0xad, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32, 0x10, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x13, 0x12, 0x11, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, - 0xa1, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, 0x10, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x92, 0x41, 0x13, - 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x2e, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, - 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, + 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x52, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0x67, 0x0a, 0x24, + 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 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, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x8a, 0x01, 0x0a, 0x26, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 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, 0x36, 0x0a, 0x16, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, + 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x16, 0x63, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, + 0x64, 0x73, 0x22, 0x6a, 0x0a, 0x27, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x91, + 0x01, 0x0a, 0x17, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x19, + 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x63, 0x6f, + 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x63, 0x6f, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x6f, 0x73, 0x74, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x68, 0x6f, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x22, 0x69, 0x0a, 0x18, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, + 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x32, 0x87, 0x14, + 0x0a, 0x0d, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, + 0xa2, 0x01, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x22, 0x22, 0x2f, 0x76, 0x31, - 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x2d, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x3a, - 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x17, 0x12, 0x15, 0x41, 0x75, 0x74, - 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x2e, 0x12, 0xda, 0x01, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, - 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, - 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, - 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, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x22, 0x1e, 0x2f, - 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, - 0x61, 0x64, 0x64, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x01, 0x2a, - 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x26, 0x12, 0x24, 0x41, 0x64, 0x64, 0x73, 0x20, - 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, - 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, - 0xd7, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, - 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x6f, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, - 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x55, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, - 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, - 0x74, 0x65, 0x6d, 0x92, 0x41, 0x23, 0x12, 0x21, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, - 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, - 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, 0xe4, 0x01, 0x0a, 0x14, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, - 0x74, 0x73, 0x12, 0x37, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x18, 0x12, 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, + 0x7b, 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x17, 0x12, 0x15, 0x47, + 0x65, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x2e, 0x12, 0x9a, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0d, 0x12, 0x0b, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x92, 0x41, 0x14, 0x12, 0x12, 0x4c, + 0x69, 0x73, 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, + 0x2e, 0x12, 0xaf, 0x01, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 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, - 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, - 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x38, 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, 0x54, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2c, 0x22, 0x21, 0x2f, - 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, - 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, - 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x24, 0x12, 0x22, 0x52, 0x65, - 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, - 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, - 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, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x0b, 0x2f, + 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x1a, 0x12, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x2e, 0x12, 0xad, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x3a, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1e, 0x32, + 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, + 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x13, + 0x12, 0x11, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x2e, 0x12, 0xa1, 0x01, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x12, 0x2a, + 0x10, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, + 0x7d, 0x92, 0x41, 0x13, 0x12, 0x11, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x73, 0x20, 0x61, 0x20, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, 0xcc, 0x01, 0x0a, 0x10, 0x41, 0x75, 0x74, 0x68, + 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, + 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 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, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4d, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x2d, 0x22, + 0x22, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, + 0x7d, 0x3a, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x2d, 0x73, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x17, 0x12, + 0x15, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x65, 0x73, 0x20, 0x61, 0x20, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x12, 0xda, 0x01, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x54, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x35, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x58, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x29, 0x22, 0x1e, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, + 0x69, 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, + 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x26, 0x12, 0x24, 0x41, + 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x48, 0x6f, 0x73, + 0x74, 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x2e, 0x12, 0xd7, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x35, 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, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x55, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x29, 0x22, 0x1e, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, 0x73, 0x65, 0x74, 0x73, 0x3a, 0x01, + 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x23, 0x12, 0x21, 0x53, 0x65, 0x74, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, 0x6f, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, 0xe4, 0x01, + 0x0a, 0x14, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, + 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x12, 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, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x38, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x48, 0x6f, 0x73, 0x74, 0x53, 0x65, 0x74, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x59, 0x82, 0xd3, 0xe4, 0x93, 0x02, + 0x2c, 0x22, 0x21, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, + 0x69, 0x64, 0x7d, 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x2d, + 0x73, 0x65, 0x74, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x24, + 0x12, 0x22, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x73, 0x20, 0x48, 0x6f, 0x73, 0x74, 0x20, 0x53, + 0x65, 0x74, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x2e, 0x12, 0x91, 0x02, 0x0a, 0x1c, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3f, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x41, 0x64, 0x64, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x34, + 0x22, 0x29, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, + 0x64, 0x7d, 0x3a, 0x61, 0x64, 0x64, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x62, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x31, 0x12, 0x2f, 0x41, 0x64, 0x64, 0x73, 0x20, 0x65, 0x78, + 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x20, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x61, + 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, 0x8e, 0x02, 0x0a, 0x1c, 0x53, 0x65, 0x74, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x3f, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, + 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 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, 0x53, 0x65, 0x74, 0x54, 0x61, 0x72, 0x67, 0x65, + 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6b, 0x82, 0xd3, + 0xe4, 0x93, 0x02, 0x34, 0x22, 0x29, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, + 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x73, 0x65, 0x74, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3a, + 0x01, 0x2a, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x2e, 0x12, 0x2c, 0x53, 0x65, 0x74, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x20, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x12, 0x9b, 0x02, 0x0a, 0x1f, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x12, 0x42, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x43, 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, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x37, 0x22, 0x2c, + 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, + 0x3a, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x2d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x62, + 0x04, 0x69, 0x74, 0x65, 0x6d, 0x92, 0x41, 0x2f, 0x12, 0x2d, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x73, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x20, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x69, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 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 ( @@ -1244,65 +1775,83 @@ func file_controller_api_services_v1_target_service_proto_rawDescGZIP() []byte { return file_controller_api_services_v1_target_service_proto_rawDescData } -var file_controller_api_services_v1_target_service_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_controller_api_services_v1_target_service_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_controller_api_services_v1_target_service_proto_goTypes = []interface{}{ - (*GetTargetRequest)(nil), // 0: controller.api.services.v1.GetTargetRequest - (*GetTargetResponse)(nil), // 1: controller.api.services.v1.GetTargetResponse - (*ListTargetsRequest)(nil), // 2: controller.api.services.v1.ListTargetsRequest - (*ListTargetsResponse)(nil), // 3: controller.api.services.v1.ListTargetsResponse - (*CreateTargetRequest)(nil), // 4: controller.api.services.v1.CreateTargetRequest - (*CreateTargetResponse)(nil), // 5: controller.api.services.v1.CreateTargetResponse - (*UpdateTargetRequest)(nil), // 6: controller.api.services.v1.UpdateTargetRequest - (*UpdateTargetResponse)(nil), // 7: controller.api.services.v1.UpdateTargetResponse - (*DeleteTargetRequest)(nil), // 8: controller.api.services.v1.DeleteTargetRequest - (*DeleteTargetResponse)(nil), // 9: controller.api.services.v1.DeleteTargetResponse - (*AddTargetHostSetsRequest)(nil), // 10: controller.api.services.v1.AddTargetHostSetsRequest - (*AddTargetHostSetsResponse)(nil), // 11: controller.api.services.v1.AddTargetHostSetsResponse - (*SetTargetHostSetsRequest)(nil), // 12: controller.api.services.v1.SetTargetHostSetsRequest - (*SetTargetHostSetsResponse)(nil), // 13: controller.api.services.v1.SetTargetHostSetsResponse - (*RemoveTargetHostSetsRequest)(nil), // 14: controller.api.services.v1.RemoveTargetHostSetsRequest - (*RemoveTargetHostSetsResponse)(nil), // 15: controller.api.services.v1.RemoveTargetHostSetsResponse - (*AuthorizeSessionRequest)(nil), // 16: controller.api.services.v1.AuthorizeSessionRequest - (*AuthorizeSessionResponse)(nil), // 17: controller.api.services.v1.AuthorizeSessionResponse - (*targets.Target)(nil), // 18: controller.api.resources.targets.v1.Target - (*fieldmaskpb.FieldMask)(nil), // 19: google.protobuf.FieldMask - (*targets.SessionAuthorization)(nil), // 20: controller.api.resources.targets.v1.SessionAuthorization + (*GetTargetRequest)(nil), // 0: controller.api.services.v1.GetTargetRequest + (*GetTargetResponse)(nil), // 1: controller.api.services.v1.GetTargetResponse + (*ListTargetsRequest)(nil), // 2: controller.api.services.v1.ListTargetsRequest + (*ListTargetsResponse)(nil), // 3: controller.api.services.v1.ListTargetsResponse + (*CreateTargetRequest)(nil), // 4: controller.api.services.v1.CreateTargetRequest + (*CreateTargetResponse)(nil), // 5: controller.api.services.v1.CreateTargetResponse + (*UpdateTargetRequest)(nil), // 6: controller.api.services.v1.UpdateTargetRequest + (*UpdateTargetResponse)(nil), // 7: controller.api.services.v1.UpdateTargetResponse + (*DeleteTargetRequest)(nil), // 8: controller.api.services.v1.DeleteTargetRequest + (*DeleteTargetResponse)(nil), // 9: controller.api.services.v1.DeleteTargetResponse + (*AddTargetHostSetsRequest)(nil), // 10: controller.api.services.v1.AddTargetHostSetsRequest + (*AddTargetHostSetsResponse)(nil), // 11: controller.api.services.v1.AddTargetHostSetsResponse + (*SetTargetHostSetsRequest)(nil), // 12: controller.api.services.v1.SetTargetHostSetsRequest + (*SetTargetHostSetsResponse)(nil), // 13: controller.api.services.v1.SetTargetHostSetsResponse + (*RemoveTargetHostSetsRequest)(nil), // 14: controller.api.services.v1.RemoveTargetHostSetsRequest + (*RemoveTargetHostSetsResponse)(nil), // 15: controller.api.services.v1.RemoveTargetHostSetsResponse + (*CredentialLibrary)(nil), // 16: controller.api.services.v1.CredentialLibrary + (*AddTargetCredentialLibrariesRequest)(nil), // 17: controller.api.services.v1.AddTargetCredentialLibrariesRequest + (*AddTargetCredentialLibrariesResponse)(nil), // 18: controller.api.services.v1.AddTargetCredentialLibrariesResponse + (*SetTargetCredentialLibrariesRequest)(nil), // 19: controller.api.services.v1.SetTargetCredentialLibrariesRequest + (*SetTargetCredentialLibrariesResponse)(nil), // 20: controller.api.services.v1.SetTargetCredentialLibrariesResponse + (*RemoveTargetCredentialLibrariesRequest)(nil), // 21: controller.api.services.v1.RemoveTargetCredentialLibrariesRequest + (*RemoveTargetCredentialLibrariesResponse)(nil), // 22: controller.api.services.v1.RemoveTargetCredentialLibrariesResponse + (*AuthorizeSessionRequest)(nil), // 23: controller.api.services.v1.AuthorizeSessionRequest + (*AuthorizeSessionResponse)(nil), // 24: controller.api.services.v1.AuthorizeSessionResponse + (*targets.Target)(nil), // 25: controller.api.resources.targets.v1.Target + (*fieldmaskpb.FieldMask)(nil), // 26: google.protobuf.FieldMask + (*targets.SessionAuthorization)(nil), // 27: controller.api.resources.targets.v1.SessionAuthorization } var file_controller_api_services_v1_target_service_proto_depIdxs = []int32{ - 18, // 0: controller.api.services.v1.GetTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 1: controller.api.services.v1.ListTargetsResponse.items:type_name -> controller.api.resources.targets.v1.Target - 18, // 2: controller.api.services.v1.CreateTargetRequest.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 3: controller.api.services.v1.CreateTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 4: controller.api.services.v1.UpdateTargetRequest.item:type_name -> controller.api.resources.targets.v1.Target - 19, // 5: controller.api.services.v1.UpdateTargetRequest.update_mask:type_name -> google.protobuf.FieldMask - 18, // 6: controller.api.services.v1.UpdateTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 7: controller.api.services.v1.AddTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 8: controller.api.services.v1.SetTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target - 18, // 9: controller.api.services.v1.RemoveTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target - 20, // 10: controller.api.services.v1.AuthorizeSessionResponse.item:type_name -> controller.api.resources.targets.v1.SessionAuthorization - 0, // 11: controller.api.services.v1.TargetService.GetTarget:input_type -> controller.api.services.v1.GetTargetRequest - 2, // 12: controller.api.services.v1.TargetService.ListTargets:input_type -> controller.api.services.v1.ListTargetsRequest - 4, // 13: controller.api.services.v1.TargetService.CreateTarget:input_type -> controller.api.services.v1.CreateTargetRequest - 6, // 14: controller.api.services.v1.TargetService.UpdateTarget:input_type -> controller.api.services.v1.UpdateTargetRequest - 8, // 15: controller.api.services.v1.TargetService.DeleteTarget:input_type -> controller.api.services.v1.DeleteTargetRequest - 16, // 16: controller.api.services.v1.TargetService.AuthorizeSession:input_type -> controller.api.services.v1.AuthorizeSessionRequest - 10, // 17: controller.api.services.v1.TargetService.AddTargetHostSets:input_type -> controller.api.services.v1.AddTargetHostSetsRequest - 12, // 18: controller.api.services.v1.TargetService.SetTargetHostSets:input_type -> controller.api.services.v1.SetTargetHostSetsRequest - 14, // 19: controller.api.services.v1.TargetService.RemoveTargetHostSets:input_type -> controller.api.services.v1.RemoveTargetHostSetsRequest - 1, // 20: controller.api.services.v1.TargetService.GetTarget:output_type -> controller.api.services.v1.GetTargetResponse - 3, // 21: controller.api.services.v1.TargetService.ListTargets:output_type -> controller.api.services.v1.ListTargetsResponse - 5, // 22: controller.api.services.v1.TargetService.CreateTarget:output_type -> controller.api.services.v1.CreateTargetResponse - 7, // 23: controller.api.services.v1.TargetService.UpdateTarget:output_type -> controller.api.services.v1.UpdateTargetResponse - 9, // 24: controller.api.services.v1.TargetService.DeleteTarget:output_type -> controller.api.services.v1.DeleteTargetResponse - 17, // 25: controller.api.services.v1.TargetService.AuthorizeSession:output_type -> controller.api.services.v1.AuthorizeSessionResponse - 11, // 26: controller.api.services.v1.TargetService.AddTargetHostSets:output_type -> controller.api.services.v1.AddTargetHostSetsResponse - 13, // 27: controller.api.services.v1.TargetService.SetTargetHostSets:output_type -> controller.api.services.v1.SetTargetHostSetsResponse - 15, // 28: controller.api.services.v1.TargetService.RemoveTargetHostSets:output_type -> controller.api.services.v1.RemoveTargetHostSetsResponse - 20, // [20:29] is the sub-list for method output_type - 11, // [11:20] is the sub-list for method input_type - 11, // [11:11] is the sub-list for extension type_name - 11, // [11:11] is the sub-list for extension extendee - 0, // [0:11] is the sub-list for field type_name + 25, // 0: controller.api.services.v1.GetTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 1: controller.api.services.v1.ListTargetsResponse.items:type_name -> controller.api.resources.targets.v1.Target + 25, // 2: controller.api.services.v1.CreateTargetRequest.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 3: controller.api.services.v1.CreateTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 4: controller.api.services.v1.UpdateTargetRequest.item:type_name -> controller.api.resources.targets.v1.Target + 26, // 5: controller.api.services.v1.UpdateTargetRequest.update_mask:type_name -> google.protobuf.FieldMask + 25, // 6: controller.api.services.v1.UpdateTargetResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 7: controller.api.services.v1.AddTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 8: controller.api.services.v1.SetTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 9: controller.api.services.v1.RemoveTargetHostSetsResponse.item:type_name -> controller.api.resources.targets.v1.Target + 16, // 10: controller.api.services.v1.AddTargetCredentialLibrariesRequest.credential_libraries:type_name -> controller.api.services.v1.CredentialLibrary + 25, // 11: controller.api.services.v1.AddTargetCredentialLibrariesResponse.item:type_name -> controller.api.resources.targets.v1.Target + 16, // 12: controller.api.services.v1.SetTargetCredentialLibrariesRequest.credential_libraries:type_name -> controller.api.services.v1.CredentialLibrary + 25, // 13: controller.api.services.v1.SetTargetCredentialLibrariesResponse.item:type_name -> controller.api.resources.targets.v1.Target + 25, // 14: controller.api.services.v1.RemoveTargetCredentialLibrariesResponse.item:type_name -> controller.api.resources.targets.v1.Target + 27, // 15: controller.api.services.v1.AuthorizeSessionResponse.item:type_name -> controller.api.resources.targets.v1.SessionAuthorization + 0, // 16: controller.api.services.v1.TargetService.GetTarget:input_type -> controller.api.services.v1.GetTargetRequest + 2, // 17: controller.api.services.v1.TargetService.ListTargets:input_type -> controller.api.services.v1.ListTargetsRequest + 4, // 18: controller.api.services.v1.TargetService.CreateTarget:input_type -> controller.api.services.v1.CreateTargetRequest + 6, // 19: controller.api.services.v1.TargetService.UpdateTarget:input_type -> controller.api.services.v1.UpdateTargetRequest + 8, // 20: controller.api.services.v1.TargetService.DeleteTarget:input_type -> controller.api.services.v1.DeleteTargetRequest + 23, // 21: controller.api.services.v1.TargetService.AuthorizeSession:input_type -> controller.api.services.v1.AuthorizeSessionRequest + 10, // 22: controller.api.services.v1.TargetService.AddTargetHostSets:input_type -> controller.api.services.v1.AddTargetHostSetsRequest + 12, // 23: controller.api.services.v1.TargetService.SetTargetHostSets:input_type -> controller.api.services.v1.SetTargetHostSetsRequest + 14, // 24: controller.api.services.v1.TargetService.RemoveTargetHostSets:input_type -> controller.api.services.v1.RemoveTargetHostSetsRequest + 17, // 25: controller.api.services.v1.TargetService.AddTargetCredentialLibraries:input_type -> controller.api.services.v1.AddTargetCredentialLibrariesRequest + 19, // 26: controller.api.services.v1.TargetService.SetTargetCredentialLibraries:input_type -> controller.api.services.v1.SetTargetCredentialLibrariesRequest + 21, // 27: controller.api.services.v1.TargetService.RemoveTargetCredentialLibraries:input_type -> controller.api.services.v1.RemoveTargetCredentialLibrariesRequest + 1, // 28: controller.api.services.v1.TargetService.GetTarget:output_type -> controller.api.services.v1.GetTargetResponse + 3, // 29: controller.api.services.v1.TargetService.ListTargets:output_type -> controller.api.services.v1.ListTargetsResponse + 5, // 30: controller.api.services.v1.TargetService.CreateTarget:output_type -> controller.api.services.v1.CreateTargetResponse + 7, // 31: controller.api.services.v1.TargetService.UpdateTarget:output_type -> controller.api.services.v1.UpdateTargetResponse + 9, // 32: controller.api.services.v1.TargetService.DeleteTarget:output_type -> controller.api.services.v1.DeleteTargetResponse + 24, // 33: controller.api.services.v1.TargetService.AuthorizeSession:output_type -> controller.api.services.v1.AuthorizeSessionResponse + 11, // 34: controller.api.services.v1.TargetService.AddTargetHostSets:output_type -> controller.api.services.v1.AddTargetHostSetsResponse + 13, // 35: controller.api.services.v1.TargetService.SetTargetHostSets:output_type -> controller.api.services.v1.SetTargetHostSetsResponse + 15, // 36: controller.api.services.v1.TargetService.RemoveTargetHostSets:output_type -> controller.api.services.v1.RemoveTargetHostSetsResponse + 18, // 37: controller.api.services.v1.TargetService.AddTargetCredentialLibraries:output_type -> controller.api.services.v1.AddTargetCredentialLibrariesResponse + 20, // 38: controller.api.services.v1.TargetService.SetTargetCredentialLibraries:output_type -> controller.api.services.v1.SetTargetCredentialLibrariesResponse + 22, // 39: controller.api.services.v1.TargetService.RemoveTargetCredentialLibraries:output_type -> controller.api.services.v1.RemoveTargetCredentialLibrariesResponse + 28, // [28:40] is the sub-list for method output_type + 16, // [16:28] 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 } func init() { file_controller_api_services_v1_target_service_proto_init() } @@ -1504,7 +2053,7 @@ func file_controller_api_services_v1_target_service_proto_init() { } } file_controller_api_services_v1_target_service_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthorizeSessionRequest); i { + switch v := v.(*CredentialLibrary); i { case 0: return &v.state case 1: @@ -1516,6 +2065,90 @@ func file_controller_api_services_v1_target_service_proto_init() { } } file_controller_api_services_v1_target_service_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddTargetCredentialLibrariesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AddTargetCredentialLibrariesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetTargetCredentialLibrariesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SetTargetCredentialLibrariesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveTargetCredentialLibrariesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RemoveTargetCredentialLibrariesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AuthorizeSessionRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_api_services_v1_target_service_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*AuthorizeSessionResponse); i { case 0: return &v.state @@ -1534,7 +2167,7 @@ func file_controller_api_services_v1_target_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_api_services_v1_target_service_proto_rawDesc, NumEnums: 0, - NumMessages: 18, + NumMessages: 25, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/gen/controller/api/services/target_service.pb.gw.go b/internal/gen/controller/api/services/target_service.pb.gw.go index ac183a727..0fb54e4ee 100644 --- a/internal/gen/controller/api/services/target_service.pb.gw.go +++ b/internal/gen/controller/api/services/target_service.pb.gw.go @@ -577,6 +577,210 @@ func local_request_TargetService_RemoveTargetHostSets_0(ctx context.Context, mar } +func request_TargetService_AddTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, client TargetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.AddTargetCredentialLibraries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TargetService_AddTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, server TargetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq AddTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.AddTargetCredentialLibraries(ctx, &protoReq) + return msg, metadata, err + +} + +func request_TargetService_SetTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, client TargetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.SetTargetCredentialLibraries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TargetService_SetTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, server TargetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq SetTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.SetTargetCredentialLibraries(ctx, &protoReq) + return msg, metadata, err + +} + +func request_TargetService_RemoveTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, client TargetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.RemoveTargetCredentialLibraries(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_TargetService_RemoveTargetCredentialLibraries_0(ctx context.Context, marshaler runtime.Marshaler, server TargetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RemoveTargetCredentialLibrariesRequest + 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); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.RemoveTargetCredentialLibraries(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterTargetServiceHandlerServer registers the http handlers for service TargetService to "mux". // UnaryRPC :call TargetServiceServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -790,6 +994,75 @@ func RegisterTargetServiceHandlerServer(ctx context.Context, mux *runtime.ServeM }) + mux.Handle("POST", pattern_TargetService_AddTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.TargetService/AddTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TargetService_AddTargetCredentialLibraries_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_AddTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_AddTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_TargetService_SetTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.TargetService/SetTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TargetService_SetTargetCredentialLibraries_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_SetTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_SetTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_TargetService_RemoveTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.TargetService/RemoveTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_TargetService_RemoveTargetCredentialLibraries_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_RemoveTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_RemoveTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1011,6 +1284,66 @@ func RegisterTargetServiceHandlerClient(ctx context.Context, mux *runtime.ServeM }) + mux.Handle("POST", pattern_TargetService_AddTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.TargetService/AddTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TargetService_AddTargetCredentialLibraries_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_AddTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_AddTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_TargetService_SetTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.TargetService/SetTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TargetService_SetTargetCredentialLibraries_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_SetTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_SetTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_TargetService_RemoveTargetCredentialLibraries_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) + rctx, err := runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.TargetService/RemoveTargetCredentialLibraries") + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_TargetService_RemoveTargetCredentialLibraries_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_TargetService_RemoveTargetCredentialLibraries_0(ctx, mux, outboundMarshaler, w, req, response_TargetService_RemoveTargetCredentialLibraries_0{resp}, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -1077,6 +1410,33 @@ func (m response_TargetService_RemoveTargetHostSets_0) XXX_ResponseBody() interf return response.Item } +type response_TargetService_AddTargetCredentialLibraries_0 struct { + proto.Message +} + +func (m response_TargetService_AddTargetCredentialLibraries_0) XXX_ResponseBody() interface{} { + response := m.Message.(*AddTargetCredentialLibrariesResponse) + return response.Item +} + +type response_TargetService_SetTargetCredentialLibraries_0 struct { + proto.Message +} + +func (m response_TargetService_SetTargetCredentialLibraries_0) XXX_ResponseBody() interface{} { + response := m.Message.(*SetTargetCredentialLibrariesResponse) + return response.Item +} + +type response_TargetService_RemoveTargetCredentialLibraries_0 struct { + proto.Message +} + +func (m response_TargetService_RemoveTargetCredentialLibraries_0) XXX_ResponseBody() interface{} { + response := m.Message.(*RemoveTargetCredentialLibrariesResponse) + return response.Item +} + var ( pattern_TargetService_GetTarget_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "")) @@ -1095,6 +1455,12 @@ var ( pattern_TargetService_SetTargetHostSets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "set-host-sets")) pattern_TargetService_RemoveTargetHostSets_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "remove-host-sets")) + + pattern_TargetService_AddTargetCredentialLibraries_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "add-credential-libraries")) + + pattern_TargetService_SetTargetCredentialLibraries_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "set-credential-libraries")) + + pattern_TargetService_RemoveTargetCredentialLibraries_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "targets", "id"}, "remove-credential-libraries")) ) var ( @@ -1115,4 +1481,10 @@ var ( forward_TargetService_SetTargetHostSets_0 = runtime.ForwardResponseMessage forward_TargetService_RemoveTargetHostSets_0 = runtime.ForwardResponseMessage + + forward_TargetService_AddTargetCredentialLibraries_0 = runtime.ForwardResponseMessage + + forward_TargetService_SetTargetCredentialLibraries_0 = runtime.ForwardResponseMessage + + forward_TargetService_RemoveTargetCredentialLibraries_0 = runtime.ForwardResponseMessage ) diff --git a/internal/gen/controller/api/services/target_service_grpc.pb.go b/internal/gen/controller/api/services/target_service_grpc.pb.go index c8ba3771c..4ae214feb 100644 --- a/internal/gen/controller/api/services/target_service_grpc.pb.go +++ b/internal/gen/controller/api/services/target_service_grpc.pb.go @@ -71,6 +71,30 @@ type TargetServiceClient interface { // returned. An error is returned if a Host Set is attempted to be // removed from the Target when the Target does not have the Host Set. RemoveTargetHostSets(ctx context.Context, in *RemoveTargetHostSetsRequest, opts ...grpc.CallOption) (*RemoveTargetHostSetsResponse, error) + // AddTargetCredentialLibraries adds Credential Libraries to this Target. + // The provided request must include the Target ID to which the Credential + // Libraries will be added. All Credential Libraries added to the provided + // Target must be a child of a Store that is in the same scope as this + // Target. If the scope or Target IDs are missing, malformed, or reference + // non-existing resources, an error is returned. An error is returned if a + // Credential Library is attempted to be added to a target that is already + // present on the Target. + AddTargetCredentialLibraries(ctx context.Context, in *AddTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*AddTargetCredentialLibrariesResponse, error) + // SetTargetCredentialLibraries sets the Target's Credential Libraries. + // Any existing Credential Libraries on the Target are deleted if they are + // not included in this request. The provided request must include the scope, + // and the Target ID on which the Credential Libraries will be set. All + // Credential Libraries in the request must be a child of a Store that is + // in the same scope as the provided Target. If any IDs are missing, + // malformed, or references a non-existing resource, an error is returned. + SetTargetCredentialLibraries(ctx context.Context, in *SetTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*SetTargetCredentialLibrariesResponse, error) + // RemoveTargetCredentialLibraries removes the Credential Libraries from the + // specified Target. The provided request must include the Target ID for the + // Target from which the Credential Libraries will be removed. If the ID is + // missing, or malformed, an error is returned. An error is returned if a + // Credential Library is attempted to be removed from the Target when the + // Target does not have the Credential Library. + RemoveTargetCredentialLibraries(ctx context.Context, in *RemoveTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*RemoveTargetCredentialLibrariesResponse, error) } type targetServiceClient struct { @@ -162,6 +186,33 @@ func (c *targetServiceClient) RemoveTargetHostSets(ctx context.Context, in *Remo return out, nil } +func (c *targetServiceClient) AddTargetCredentialLibraries(ctx context.Context, in *AddTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*AddTargetCredentialLibrariesResponse, error) { + out := new(AddTargetCredentialLibrariesResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.TargetService/AddTargetCredentialLibraries", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *targetServiceClient) SetTargetCredentialLibraries(ctx context.Context, in *SetTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*SetTargetCredentialLibrariesResponse, error) { + out := new(SetTargetCredentialLibrariesResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.TargetService/SetTargetCredentialLibraries", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *targetServiceClient) RemoveTargetCredentialLibraries(ctx context.Context, in *RemoveTargetCredentialLibrariesRequest, opts ...grpc.CallOption) (*RemoveTargetCredentialLibrariesResponse, error) { + out := new(RemoveTargetCredentialLibrariesResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.TargetService/RemoveTargetCredentialLibraries", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // TargetServiceServer is the server API for TargetService service. // All implementations must embed UnimplementedTargetServiceServer // for forward compatibility @@ -219,6 +270,30 @@ type TargetServiceServer interface { // returned. An error is returned if a Host Set is attempted to be // removed from the Target when the Target does not have the Host Set. RemoveTargetHostSets(context.Context, *RemoveTargetHostSetsRequest) (*RemoveTargetHostSetsResponse, error) + // AddTargetCredentialLibraries adds Credential Libraries to this Target. + // The provided request must include the Target ID to which the Credential + // Libraries will be added. All Credential Libraries added to the provided + // Target must be a child of a Store that is in the same scope as this + // Target. If the scope or Target IDs are missing, malformed, or reference + // non-existing resources, an error is returned. An error is returned if a + // Credential Library is attempted to be added to a target that is already + // present on the Target. + AddTargetCredentialLibraries(context.Context, *AddTargetCredentialLibrariesRequest) (*AddTargetCredentialLibrariesResponse, error) + // SetTargetCredentialLibraries sets the Target's Credential Libraries. + // Any existing Credential Libraries on the Target are deleted if they are + // not included in this request. The provided request must include the scope, + // and the Target ID on which the Credential Libraries will be set. All + // Credential Libraries in the request must be a child of a Store that is + // in the same scope as the provided Target. If any IDs are missing, + // malformed, or references a non-existing resource, an error is returned. + SetTargetCredentialLibraries(context.Context, *SetTargetCredentialLibrariesRequest) (*SetTargetCredentialLibrariesResponse, error) + // RemoveTargetCredentialLibraries removes the Credential Libraries from the + // specified Target. The provided request must include the Target ID for the + // Target from which the Credential Libraries will be removed. If the ID is + // missing, or malformed, an error is returned. An error is returned if a + // Credential Library is attempted to be removed from the Target when the + // Target does not have the Credential Library. + RemoveTargetCredentialLibraries(context.Context, *RemoveTargetCredentialLibrariesRequest) (*RemoveTargetCredentialLibrariesResponse, error) mustEmbedUnimplementedTargetServiceServer() } @@ -253,6 +328,15 @@ func (UnimplementedTargetServiceServer) SetTargetHostSets(context.Context, *SetT func (UnimplementedTargetServiceServer) RemoveTargetHostSets(context.Context, *RemoveTargetHostSetsRequest) (*RemoveTargetHostSetsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RemoveTargetHostSets not implemented") } +func (UnimplementedTargetServiceServer) AddTargetCredentialLibraries(context.Context, *AddTargetCredentialLibrariesRequest) (*AddTargetCredentialLibrariesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AddTargetCredentialLibraries not implemented") +} +func (UnimplementedTargetServiceServer) SetTargetCredentialLibraries(context.Context, *SetTargetCredentialLibrariesRequest) (*SetTargetCredentialLibrariesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetTargetCredentialLibraries not implemented") +} +func (UnimplementedTargetServiceServer) RemoveTargetCredentialLibraries(context.Context, *RemoveTargetCredentialLibrariesRequest) (*RemoveTargetCredentialLibrariesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RemoveTargetCredentialLibraries not implemented") +} func (UnimplementedTargetServiceServer) mustEmbedUnimplementedTargetServiceServer() {} // UnsafeTargetServiceServer may be embedded to opt out of forward compatibility for this service. @@ -428,6 +512,60 @@ func _TargetService_RemoveTargetHostSets_Handler(srv interface{}, ctx context.Co return interceptor(ctx, in, info, handler) } +func _TargetService_AddTargetCredentialLibraries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddTargetCredentialLibrariesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TargetServiceServer).AddTargetCredentialLibraries(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.TargetService/AddTargetCredentialLibraries", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TargetServiceServer).AddTargetCredentialLibraries(ctx, req.(*AddTargetCredentialLibrariesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TargetService_SetTargetCredentialLibraries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetTargetCredentialLibrariesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TargetServiceServer).SetTargetCredentialLibraries(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.TargetService/SetTargetCredentialLibraries", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TargetServiceServer).SetTargetCredentialLibraries(ctx, req.(*SetTargetCredentialLibrariesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _TargetService_RemoveTargetCredentialLibraries_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveTargetCredentialLibrariesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(TargetServiceServer).RemoveTargetCredentialLibraries(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.TargetService/RemoveTargetCredentialLibraries", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(TargetServiceServer).RemoveTargetCredentialLibraries(ctx, req.(*RemoveTargetCredentialLibrariesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // TargetService_ServiceDesc is the grpc.ServiceDesc for TargetService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -471,6 +609,18 @@ var TargetService_ServiceDesc = grpc.ServiceDesc{ MethodName: "RemoveTargetHostSets", Handler: _TargetService_RemoveTargetHostSets_Handler, }, + { + MethodName: "AddTargetCredentialLibraries", + Handler: _TargetService_AddTargetCredentialLibraries_Handler, + }, + { + MethodName: "SetTargetCredentialLibraries", + Handler: _TargetService_SetTargetCredentialLibraries_Handler, + }, + { + MethodName: "RemoveTargetCredentialLibraries", + Handler: _TargetService_RemoveTargetCredentialLibraries_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "controller/api/services/v1/target_service.proto", diff --git a/internal/proto/local/controller/api/resources/credentiallibraries/v1/credential_library.proto b/internal/proto/local/controller/api/resources/credentiallibraries/v1/credential_library.proto new file mode 100644 index 000000000..509c8336d --- /dev/null +++ b/internal/proto/local/controller/api/resources/credentiallibraries/v1/credential_library.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package controller.api.resources.credentiallibraries.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries;credentiallibraries"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/struct.proto"; +import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/custom_options/v1/options.proto"; + +// CredentialLibrary contains all fields related to an Credential Library resource +message CredentialLibrary { + // Output only. The ID of the Credential Library. + string id = 10; + + // The ID of the Credential Store of which this Credential Library is a part. + string credential_store_id = 20 [json_name = "credential_store_id"]; + + // Output only. Scope information for this Credential Library. + resources.scopes.v1.ScopeInfo scope = 30; + + // Optional name for identification purposes. + google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "name" that: "Name" }]; + + // Optional user-set description for identification purposes. + google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "description" that: "Description" }]; + + // Output only. The time this resource was created. + google.protobuf.Timestamp created_time = 60 [json_name = "created_time"]; + + // Output only. The time this resource was last updated. + google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"]; + + // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 80; + + // The Credential Library type. + string type = 90; + + // The attributes that are applicable for the specific Credential Library type. + google.protobuf.Struct attributes = 100 [(custom_options.v1.generate_sdk_option) = true]; + + // Output only. The available actions on this resource for this user. + repeated string authorized_actions = 300 [json_name = "authorized_actions"]; +} + +// The attributes of a vault typed Credential Library. +message VaultCredentialLibraryAttributes { + // The path in Vault to request credentials from. + google.protobuf.StringValue path = 10 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.path" that: "VaultPath" }]; + + // The HTTP method the library uses to communicate with Vault. + google.protobuf.StringValue http_method = 20 [json_name = "http_method", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.http_method" that: "HttpMethod" }]; + + // The body of the HTTP request the library sends to vault. When set http_method must be "POST" + google.protobuf.StringValue http_request_body = 30 [json_name = "http_request_body", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.http_request_body" that: "HttpRequestBody" }]; +} \ No newline at end of file diff --git a/internal/proto/local/controller/api/resources/credentialstores/v1/credential_store.proto b/internal/proto/local/controller/api/resources/credentialstores/v1/credential_store.proto new file mode 100644 index 000000000..d4167851c --- /dev/null +++ b/internal/proto/local/controller/api/resources/credentialstores/v1/credential_store.proto @@ -0,0 +1,87 @@ +syntax = "proto3"; + +package controller.api.resources.credentialstores.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores;credentialstores"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/struct.proto"; +import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/custom_options/v1/options.proto"; + +// CredentialStore contains all fields related to an Credential Store resource +message CredentialStore { + // Output only. The ID of the Credential Store. + string id = 10; + + // The ID of the Scope of which this Credential Store is a part. + string scope_id = 20 [json_name = "scope_id"]; + + // Output only. Scope information for this Credential Store. + resources.scopes.v1.ScopeInfo scope = 30; + + // Optional name for identification purposes. + google.protobuf.StringValue name = 40 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "name" that: "Name" }]; + + // Optional user-set description for identification purposes. + google.protobuf.StringValue description = 50 [(custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "description" that: "Description" }]; + + // Output only. The time this resource was created. + google.protobuf.Timestamp created_time = 60 [json_name = "created_time"]; + + // Output only. The time this resource was last updated. + google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"]; + + // Version is used in mutation requests, after the initial creation, to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 80; + + // The Credential Store type. + string type = 90; + + // The attributes that are applicable for the specific Credential Store type. + google.protobuf.Struct attributes = 100 [(custom_options.v1.generate_sdk_option) = true]; + + // Output only. The available actions on this resource for this user. + repeated string authorized_actions = 300 [json_name = "authorized_actions"]; + + // Output only. The authorized actions for the scope's collections. + map authorized_collection_actions = 310 [json_name = "authorized_collection_actions"]; +} + +// The attributes of a vault typed Credential Store. +message VaultCredentialStoreAttributes { + // The complete url address of vault. + google.protobuf.StringValue address = 10 [json_name = "address", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.address" that: "VaultAddress" }]; + + // The namespace of vault used by this store + google.protobuf.StringValue namespace = 20 [json_name = "namespace", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.namespace" that: "Namespace" }]; + + // The PEM encoded CA cert to verify the vault server's ssl certificate + google.protobuf.StringValue ca_cert = 30 [json_name = "ca_cert", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.ca_cert" that: "CaCert" }]; + + // The value to use as the SNI host when connecting to vault via TLS. + google.protobuf.StringValue tls_server_name = 40 [json_name = "tls_server_name", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.tls_server_name" that: "TlsServerName" }]; + + // When set to true verification of the TLS certificate is disabled. + google.protobuf.BoolValue tls_skip_verify = 50 [json_name = "tls_skip_verify", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.tls_skip_verify" that: "TlsSkipVerify" }]; + + // Input only. The current vault token used by this credential store for creating new credentials. + google.protobuf.StringValue token = 60 [json_name = "token", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.token" that: "Token" }]; + + // Output only. The hmac value of the vault token used by this credential store. + string token_hmac = 70 [json_name = "token_hmac"]; + + // Input only. A PEM encoded client certificate for vault with an + // optional private key included in the bundle. It is an error to include + // the private key in this bundle as well as setting the certificate_key + // field. + google.protobuf.StringValue client_certificate = 80 [json_name = "client_certificate", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.client_certificate" that: "Certificate" }]; + + // Input only. A client certificate private key. + google.protobuf.StringValue client_certificate_key = 90 [json_name = "client_certificate_key", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = { this: "attributes.client_certificate_key" that: "CertificateKey" }]; + + // Output only. The hmac value of the private key used by the credential store. + string client_certificate_key_hmac = 100 [json_name = "client_certificate_key_hmac"]; +} diff --git a/internal/proto/local/controller/api/resources/targets/v1/target.proto b/internal/proto/local/controller/api/resources/targets/v1/target.proto index 64ad58274..0c149c750 100644 --- a/internal/proto/local/controller/api/resources/targets/v1/target.proto +++ b/internal/proto/local/controller/api/resources/targets/v1/target.proto @@ -18,6 +18,35 @@ message HostSet{ string host_catalog_id = 20 [json_name="host_catalog_id"]; } +message CredentialLibrary{ + // The ID of the Credential Library. + string id = 10; + + // Output only. The name of the Credential Library. + string name = 20; + + // Output only. The description of the Credential Library. + string description = 30; + + // Output only. The Credential Store to which this Credential Library belongs. + string credential_store_id = 40 [json_name="credential_store_id"]; + + // The library's purpose for this target. + string purpose = 50; + + // Output only. The type of the credential library. + string type = 60; +} + +// A credential for a session. +message SessionCredential { + // Output only. The library which generated this credential. + CredentialLibrary credential_library = 1; + + // Output only. The secret of this credential base64 encoded. + string secret = 2; +} + // Target contains all fields related to a Target resource message Target { // Output only. The ID of the resource. @@ -63,6 +92,12 @@ message Target { // Optional boolean expression to filter the workers that are allowed to satisfy this request. google.protobuf.StringValue worker_filter = 140 [json_name="worker_filter", (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.mask_mapping) = {this: "worker_filter" that: "WorkerFilter"}]; + // The IDs of the credential libraries associated with this Target. + repeated string credential_library_ids = 150 [json_name="credential_library_ids"]; + + // Output only. The credential libraries associated with this Target. + repeated CredentialLibrary credential_libraries = 160 [json_name="credential_libraries"]; + // The attributes that are applicable for the specific Target. google.protobuf.Struct attributes = 200 [(custom_options.v1.generate_sdk_option) = true]; @@ -149,4 +184,7 @@ message SessionAuthorization { // Output only. The endpoint address that the worker will connect to, useful for setting TLS parameters. string endpoint = 100; + + // Output only. The credentials for this session. + repeated SessionCredential credentials = 110 [json_name="credentials"]; } \ No newline at end of file diff --git a/internal/proto/local/controller/api/services/v1/credential_library_service.proto b/internal/proto/local/controller/api/services/v1/credential_library_service.proto new file mode 100644 index 000000000..080d516b8 --- /dev/null +++ b/internal/proto/local/controller/api/services/v1/credential_library_service.proto @@ -0,0 +1,125 @@ +syntax = "proto3"; + +package controller.api.services.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/services;services"; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/field_mask.proto"; +import "controller/api/resources/credentiallibraries/v1/credential_library.proto"; + +service CredentialLibraryService { + // GetCredentialLibrary returns a stored Credential Library if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + rpc GetCredentialLibrary(GetCredentialLibraryRequest) returns (GetCredentialLibraryResponse) { + option (google.api.http) = { + get: "/v1/credential-libraries/{id}" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Gets a single Credential Library." + }; + } + + // ListCredentialLibraries returns a list of stored Credential Libraries which are in the + // provided scope. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + rpc ListCredentialLibraries(ListCredentialLibrariesRequest) returns (ListCredentialLibrariesResponse) { + option (google.api.http) = { + get: "/v1/credential-libraries" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Lists all Credential Library." + }; + } + + // CreateCredentialLibrary creates and stores an Credential Library in boundary. The + // provided request must include the scope in which the Credential Library will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Library in the same scope, an error is returned. + rpc CreateCredentialLibrary(CreateCredentialLibraryRequest) returns (CreateCredentialLibraryResponse) { + option (google.api.http) = { + post: "/v1/credential-libraries" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Creates a single Credential Library." + }; + } + + // UpdateCredentialLibrary updates an existing Credential Library in boundary. The provided + // Credential Library 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Library id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Library in the parent scope. + rpc UpdateCredentialLibrary(UpdateCredentialLibraryRequest) returns (UpdateCredentialLibraryResponse) { + option (google.api.http) = { + patch: "/v1/credential-libraries/{id}" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Updates a Credential Library." + }; + } + + // DeleteCredentialLibrary removes an Credential Library from Boundary. If the Credential Library id + // is malformed or not provided an error is returned. + rpc DeleteCredentialLibrary(DeleteCredentialLibraryRequest) returns (DeleteCredentialLibraryResponse) { + option (google.api.http) = { + delete: "/v1/credential-libraries/{id}" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Deletes a Credential Library" + }; + } +} + +message GetCredentialLibraryRequest { + string id = 1; +} + +message GetCredentialLibraryResponse { + resources.credentiallibraries.v1.CredentialLibrary item = 1; +} + +message ListCredentialLibrariesRequest { + string credential_store_id = 1 [json_name = "scope_id"]; + string filter = 30 [json_name = "filter"]; +} + +message ListCredentialLibrariesResponse { + repeated resources.credentiallibraries.v1.CredentialLibrary items = 1; +} + +message CreateCredentialLibraryRequest { + resources.credentiallibraries.v1.CredentialLibrary item = 1; +} + +message CreateCredentialLibraryResponse { + string uri = 1; + resources.credentiallibraries.v1.CredentialLibrary item = 2; +} + +message UpdateCredentialLibraryRequest { + string id = 1; + resources.credentiallibraries.v1.CredentialLibrary item = 2; + google.protobuf.FieldMask update_mask = 3 [json_name = "update_mask"]; +} + +message UpdateCredentialLibraryResponse { + resources.credentiallibraries.v1.CredentialLibrary item = 1; +} + +message DeleteCredentialLibraryRequest { + string id = 1; +} + +message DeleteCredentialLibraryResponse {} diff --git a/internal/proto/local/controller/api/services/v1/credential_store_service.proto b/internal/proto/local/controller/api/services/v1/credential_store_service.proto new file mode 100644 index 000000000..755b92d26 --- /dev/null +++ b/internal/proto/local/controller/api/services/v1/credential_store_service.proto @@ -0,0 +1,126 @@ +syntax = "proto3"; + +package controller.api.services.v1; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/services;services"; + +import "protoc-gen-openapiv2/options/annotations.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/field_mask.proto"; +import "controller/api/resources/credentialstores/v1/credential_store.proto"; + +service CredentialStoreService { + // GetCredentialStore returns a stored Credential Store if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + rpc GetCredentialStore(GetCredentialStoreRequest) returns (GetCredentialStoreResponse) { + option (google.api.http) = { + get: "/v1/credential-stores/{id}" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Gets a single Credential Store." + }; + } + + // ListCredentialStores returns a list of stored Credential Stores which are in the + // provided scope. The request must include the scope id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + rpc ListCredentialStores(ListCredentialStoresRequest) returns (ListCredentialStoresResponse) { + option (google.api.http) = { + get: "/v1/credential-stores" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Lists all Credential Stores." + }; + } + + // CreateCredentialStore creates and stores an Credential Store in boundary. The + // provided request must include the scope in which the Credential Store will be + // created. If the scope id is missing, malformed or referencing a + // non existing resource an error is returned. If a name is provided that is + // in use in another Credential Store in the same scope, an error is returned. + rpc CreateCredentialStore(CreateCredentialStoreRequest) returns (CreateCredentialStoreResponse) { + option (google.api.http) = { + post: "/v1/credential-stores" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Creates a single Credential Store." + }; + } + + // UpdateCredentialStore updates an existing Credential Store in boundary. The provided + // Credential Store 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 + // a field's value, include the field in the update mask and don't set it + // in the provided user. An error is returned if the Credential Store id is missing + // or reference a non existing resource. An error is also returned if the + // request attempts to update the name to one that is already in use by + // another Credential Store in the parent scope. + rpc UpdateCredentialStore(UpdateCredentialStoreRequest) returns (UpdateCredentialStoreResponse) { + option (google.api.http) = { + patch: "/v1/credential-stores/{id}" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Updates a Credential Store." + }; + } + + // DeleteCredentialStore removes a Credential Store from Boundary. If the Credential Store id + // is malformed or not provided an error is returned. + rpc DeleteCredentialStore(DeleteCredentialStoreRequest) returns (DeleteCredentialStoreResponse) { + option (google.api.http) = { + delete: "/v1/credential-stores/{id}" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Deletes a CredentialStore" + }; + } +} + +message GetCredentialStoreRequest { + string id = 1; +} + +message GetCredentialStoreResponse { + resources.credentialstores.v1.CredentialStore item = 1; +} + +message ListCredentialStoresRequest { + string scope_id = 1 [json_name = "scope_id"]; + bool recursive = 20 [json_name = "recursive"]; + string filter = 30 [json_name = "filter"]; +} + +message ListCredentialStoresResponse { + repeated resources.credentialstores.v1.CredentialStore items = 1; +} + +message CreateCredentialStoreRequest { + resources.credentialstores.v1.CredentialStore item = 1; +} + +message CreateCredentialStoreResponse { + string uri = 1; + resources.credentialstores.v1.CredentialStore item = 2; +} + +message UpdateCredentialStoreRequest { + string id = 1; + resources.credentialstores.v1.CredentialStore item = 2; + google.protobuf.FieldMask update_mask = 3 [json_name = "update_mask"]; +} + +message UpdateCredentialStoreResponse { + resources.credentialstores.v1.CredentialStore item = 1; +} + +message DeleteCredentialStoreRequest { + string id = 1; +} + +message DeleteCredentialStoreResponse {} diff --git a/internal/proto/local/controller/api/services/v1/target_service.proto b/internal/proto/local/controller/api/services/v1/target_service.proto index 0199661ca..96d61d0e4 100644 --- a/internal/proto/local/controller/api/services/v1/target_service.proto +++ b/internal/proto/local/controller/api/services/v1/target_service.proto @@ -149,6 +149,60 @@ service TargetService { }; } + // AddTargetCredentialLibraries adds Credential Libraries to this Target. + // The provided request must include the Target ID to which the Credential + // Libraries will be added. All Credential Libraries added to the provided + // Target must be a child of a Store that is in the same scope as this + // Target. If the scope or Target IDs are missing, malformed, or reference + // non-existing resources, an error is returned. An error is returned if a + // Credential Library is attempted to be added to a target that is already + // present on the Target. + rpc AddTargetCredentialLibraries(AddTargetCredentialLibrariesRequest) returns (AddTargetCredentialLibrariesResponse) { + option (google.api.http) = { + post: "/v1/targets/{id}:add-credential-libraries" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Adds existing Credential Libraries to a Target." + }; + } + + // SetTargetCredentialLibraries sets the Target's Credential Libraries. + // Any existing Credential Libraries on the Target are deleted if they are + // not included in this request. The provided request must include the scope, + // and the Target ID on which the Credential Libraries will be set. All + // Credential Libraries in the request must be a child of a Store that is + // in the same scope as the provided Target. If any IDs are missing, + // malformed, or references a non-existing resource, an error is returned. + rpc SetTargetCredentialLibraries(SetTargetCredentialLibrariesRequest) returns (SetTargetCredentialLibrariesResponse) { + option (google.api.http) = { + post: "/v1/targets/{id}:set-credential-libraries" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Sets the Credential Libraries on the Target." + }; + } + + // RemoveTargetCredentialLibraries removes the Credential Libraries from the + // specified Target. The provided request must include the Target ID for the + // Target from which the Credential Libraries will be removed. If the ID is + // missing, or malformed, an error is returned. An error is returned if a + // Credential Library is attempted to be removed from the Target when the + // Target does not have the Credential Library. + rpc RemoveTargetCredentialLibraries(RemoveTargetCredentialLibrariesRequest) returns (RemoveTargetCredentialLibrariesResponse) { + option (google.api.http) = { + post: "/v1/targets/{id}:remove-credential-libraries" + body: "*" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Removes Credential Libraries from the Target." + }; + } + } message GetTargetRequest { @@ -230,6 +284,59 @@ message RemoveTargetHostSetsResponse { api.resources.targets.v1.Target item = 1; } +message CredentialLibrary { + // The id of the credential library. + string id = 1; + // The purpose of the credential library for this target. Currently this can + // only be "application" or left empty (which defaults the value to + // "application"). + string purpose = 2; +} + +// The ids from both credential_library_ids and credential_libraries are used. +// When using credential_library_ids the default value "application" is used +// for the purpose. +message AddTargetCredentialLibrariesRequest { + string id = 1; + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; + repeated string credential_library_ids = 3 [json_name="credential_library_ids"]; + repeated CredentialLibrary credential_libraries = 4 [json_name="credential_libraries"]; +} + +message AddTargetCredentialLibrariesResponse { + api.resources.targets.v1.Target item = 1; +} + +// The ids from both credential_library_ids and credential_libraries are used. +// When using credential_library_ids the default value "application" is used +// for the purpose. +message SetTargetCredentialLibrariesRequest { + string id = 1; + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; + repeated string credential_library_ids = 3 [json_name="credential_library_ids"]; + repeated CredentialLibrary credential_libraries = 4 [json_name="credential_libraries"]; +} + +message SetTargetCredentialLibrariesResponse { + api.resources.targets.v1.Target item = 1; +} + +message RemoveTargetCredentialLibrariesRequest { + string id = 1; + // Version is used to ensure this resource has not changed. + // The mutation will fail if the version does not match the latest known good version. + uint32 version = 2; + repeated string credential_library_ids = 3 [json_name="credential_library_ids"]; +} + +message RemoveTargetCredentialLibrariesResponse { + api.resources.targets.v1.Target item = 1; +} + message AuthorizeSessionRequest { // The ID of the target. Required unless some combination of scope_id/scope_name and name are set. string id = 1; diff --git a/internal/proto/local/controller/storage/credential/store/v1/credential.proto b/internal/proto/local/controller/storage/credential/store/v1/credential.proto new file mode 100644 index 000000000..30b0dde8e --- /dev/null +++ b/internal/proto/local/controller/storage/credential/store/v1/credential.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the credential package. +package controller.storage.credential.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/credential/store;store"; + +message Store { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The scope_id of the owning scope and must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 2; +} + +message Library { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The store_id of the owning store and must be set. + // @inject_tag: `gorm:"not_null"` + string store_id = 2; +} + +message Credential { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; +} + +message Static { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The store_id of the owning store and must be set. + // @inject_tag: `gorm:"not_null"` + string store_id = 2; +} + +message Dynamic { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The library_id of the owning library and must be set. + // @inject_tag: `gorm:"not_null"` + string library_id = 2; +} diff --git a/internal/proto/local/controller/storage/credential/vault/store/v1/vault.proto b/internal/proto/local/controller/storage/credential/vault/store/v1/vault.proto new file mode 100644 index 000000000..287577e20 --- /dev/null +++ b/internal/proto/local/controller/storage/credential/vault/store/v1/vault.proto @@ -0,0 +1,269 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the vault +// credential package. +package controller.storage.credential.vault.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/credential/vault/store;store"; + +import "controller/storage/timestamp/v1/timestamp.proto"; +import "controller/custom_options/v1/options.proto"; + +message CredentialStore { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // The create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 2; + + // The update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 3; + + // The delete_time is set by the application. + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp delete_time = 4; + + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + string name = 5 [(custom_options.v1.mask_mapping) = {this:"Name" that: "name"}]; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 6 [(custom_options.v1.mask_mapping) = {this:"Description" that: "description"}]; + + // The scope_id of the owning scope. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 7; + + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + uint32 version = 8; + + // vault_address is the address of the vault server. This should be a + // complete URL. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string vault_address = 9 [(custom_options.v1.mask_mapping) = {this:"VaultAddress" that: "attributes.address"}]; + + // namespace is the vault namespace the credential store should use. + // It is optional. + // @inject_tag: `gorm:"default:null"` + string namespace = 10 [(custom_options.v1.mask_mapping) = {this:"Namespace" that: "attributes.namespace"}]; + + // ca_cert is a single PEM-encoded CA certificate to verify the Vault + // server's SSL certificate. + // It is optional. + // @inject_tag: `gorm:"default:null"` + bytes ca_cert = 11 [(custom_options.v1.mask_mapping) = {this:"CaCert" that: "attributes.ca_cert"}]; + + // tls_server_name is the name to use as the SNI host when connecting to + // Vault via TLS. + // It is optional. + // @inject_tag: `gorm:"default:null"` + string tls_server_name = 12 [(custom_options.v1.mask_mapping) = {this:"TlsServerName" that: "attributes.tls_server_name"}]; + + // tls_skip_verify disables verification of TLS certificates. Using this + // option is highly discouraged as it decreases the security of data + // transmissions to and from the Vault server. + // @inject_tag: `gorm:"default:false"` + bool tls_skip_verify = 13 [(custom_options.v1.mask_mapping) = {this:"TlsSkipVerify" that: "attributes.tls_skip_verify"}]; +} + +message Token { + // token_hmac is the hmac-sha256 value of the token. The hmac key is the + // blake2b-256 value of the token accessor. + // @inject_tag: `gorm:"primary_key"` + bytes token_hmac = 1; + + // token is the plain-text of the token data. We are not storing this + // plain-text token in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,token_data"` + bytes token = 2 [(custom_options.v1.mask_mapping) = {this:"Token" that: "attributes.token"}]; + + // ct_token is the ciphertext of the token data. It is stored in the database. + // @inject_tag: `gorm:"column:token;not_null" wrapping:"ct,token_data"` + bytes ct_token = 3; + + // store_id is the ID of the owning vault credential store. A vault + // credential store can have 0 or 1 token. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string store_id = 4; + + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 5; + + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 6; + + // last_renewal_time is the time the token was last renewed with Vault. + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp last_renewal_time = 8; + + // expiration_time is the time the token is expected to expire. + // expiration_time is calculated when the token is renewed. + // + // The calculation is: + // expiration_time := time.Now().Add(LeaseDuration * time.Second) + // LeaseDuration is a value returned by Vault when the token is renewed. + // + // https://www.vaultproject.io/api-docs/auth/token#renew-a-token-self + // + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp expiration_time = 9; + + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string key_id = 10; + + // The status of the token. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string status = 11; +} + +message ClientCertificate { + // store_id is the ID of the owning vault credential store. A vault + // credential store can have 0 or 1 client certificate. + // @inject_tag: `gorm:"primary_key"` + string store_id = 1; + + // certificate is the PEM encoded certificate. + // It must be set. + // @inject_tag: `gorm:"not_null"` + bytes certificate = 2 [(custom_options.v1.mask_mapping) = {this:"Certificate" that: "attributes.client_certificate"}]; + + // certificate_key is the plain-text of the certificate key data. We are + // not storing this plain-text key in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,certificate_key_data"` + bytes certificate_key = 3 [(custom_options.v1.mask_mapping) = {this:"CertificateKey" that: "attributes.client_certificate_key"}]; + + // ct_certificate_key is the ciphertext of the certificate key data. It + // is stored in the database. + // @inject_tag: `gorm:"column:certificate_key;not_null" wrapping:"ct,certificate_key_data"` + bytes ct_certificate_key = 4; + + // certificate_key_hmac is a sha256-hmac of the unencrypted certificate_key that + // is returned from the API for read. It is recalculated everytime the raw + // certificate_key is updated. + // @inject_tag: `gorm:"not_null"` + bytes certificate_key_hmac = 130; + + // The key_id of the kms database key used for encrypting this entry. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string key_id = 10; +} + +message CredentialLibrary { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 2; + + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 3; + + // name is optional. If set, it must be unique within scope_id. + // @inject_tag: `gorm:"default:null"` + string name = 4 [(custom_options.v1.mask_mapping) = {this:"Name" that: "name"}]; + + // description is optional. + // @inject_tag: `gorm:"default:null"` + string description = 5 [(custom_options.v1.mask_mapping) = {this:"Description" that: "description"}]; + + // store_id of the owning vault credential store. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string store_id = 6; + + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + uint32 version = 7; + + // vault_path is the path in Vault to request credentials from. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string vault_path = 8 [(custom_options.v1.mask_mapping) = {this:"VaultPath" that: "attributes.path"}]; + + // The HTTP method the library uses to communicate with Vault. + // It must be set. Can only be GET or POST. + // @inject_tag: `gorm:"not_null"` + string http_method = 9 [(custom_options.v1.mask_mapping) = {this:"HttpMethod" that: "attributes.http_method"}]; + + // The body of the HTTP request the library sends to Vault. + // Can only be set if http_method is POST. + // @inject_tag: `gorm:"default:null"` + bytes http_request_body = 10 [(custom_options.v1.mask_mapping) = {this:"HttpRequestBody" that: "attributes.http_request_body"}]; +} + +message Credential { + // public_id is a surrogate key suitable for use in a public API. + // @inject_tag: `gorm:"primary_key"` + string public_id = 1; + + // library_id of the owning vault credential library. + // It must be set. + string library_id = 2; + + // session_id of the session the credential was created for. + // It must be set. + string session_id = 3; + + // token_hmac is the foreign key of the token used to acquire the secret. + // @inject_tag: `gorm:"not_null"` + bytes token_hmac = 4; + + // create_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 5; + + // update_time is set by the database. + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 6; + + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + uint32 version = 7; + + // external_id is the lease ID returned from Vault. + // @inject_tag: `gorm:"not_null"` + string external_id = 8; + + // last_renewal_time is the time the lease was last renewed with Vault. + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp last_renewal_time = 9; + + // expiration_time is the time the lease is expected to expire. + // expiration_time is calculated when the credential is initially + // retrieved and whenever the credential's lease is renewed. + // + // The calculation is: + // expiration_time := time.Now().Add(LeaseDuration * time.Second) + // LeaseDuration is a value returned by Vault when the credential is + // retrieved or the lease for the credential is renewed. + // + // https://www.vaultproject.io/docs/concepts/lease + // + // @inject_tag: `gorm:"default:null"` + timestamp.v1.Timestamp expiration_time = 10; + + // is_renewable indicates if the lease can be renewed. + // @inject_tag: `gorm:"default:false"` + bool is_renewable = 11; + + // The status of the credential. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string status = 12; +} diff --git a/internal/proto/local/controller/storage/target/store/v1/target.proto b/internal/proto/local/controller/storage/target/store/v1/target.proto index b66b6e4d4..837b66ae1 100644 --- a/internal/proto/local/controller/storage/target/store/v1/target.proto +++ b/internal/proto/local/controller/storage/target/store/v1/target.proto @@ -135,4 +135,22 @@ message TcpTarget { this: "WorkerFilter" that: "worker_filter" }]; -} \ No newline at end of file +} + +message CredentialLibrary { + // target_id of the Target + // @inject_tag: gorm:"primary_key" + string target_id = 10; + + // credential_library_id of the CredentialLibrary + // @inject_tag: gorm:"primary_key" + string credential_library_id = 20; + + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + string credential_purpose = 30; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 40; +} diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go index 0f727de05..29f6f92c7 100644 --- a/internal/scheduler/scheduler_test.go +++ b/internal/scheduler/scheduler_test.go @@ -197,7 +197,7 @@ func TestScheduler_RegisterJob(t *testing.T) { { name: "valid", job: testJob{ - name: "name", + name: "valid", description: "description", }, }, @@ -259,7 +259,7 @@ func TestScheduler_RegisterJob(t *testing.T) { t.Run("multiple-schedulers-registering-same-job", func(t *testing.T) { assert, require := assert.New(t), require.New(t) tj := testJob{ - name: "name", + name: "multiple-schedulers-registering-same-job", description: "description", } err := sched.RegisterJob(context.Background(), tj) diff --git a/internal/servers/controller/common/common.go b/internal/servers/controller/common/common.go index 0ec1d0e39..1c07bfa68 100644 --- a/internal/servers/controller/common/common.go +++ b/internal/servers/controller/common/common.go @@ -3,6 +3,7 @@ package common import ( "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers" @@ -11,12 +12,13 @@ import ( ) type ( - AuthTokenRepoFactory = oidc.AuthTokenRepoFactory - IamRepoFactory func() (*iam.Repository, error) - OidcAuthRepoFactory = oidc.OidcRepoFactory - PasswordAuthRepoFactory func() (*password.Repository, error) - ServersRepoFactory func() (*servers.Repository, error) - StaticRepoFactory func() (*static.Repository, error) - SessionRepoFactory func() (*session.Repository, error) - TargetRepoFactory func() (*target.Repository, error) + AuthTokenRepoFactory = oidc.AuthTokenRepoFactory + VaultCredentialRepoFactory = func() (*vault.Repository, error) + IamRepoFactory func() (*iam.Repository, error) + OidcAuthRepoFactory = oidc.OidcRepoFactory + PasswordAuthRepoFactory func() (*password.Repository, error) + ServersRepoFactory func() (*servers.Repository, error) + StaticRepoFactory func() (*static.Repository, error) + SessionRepoFactory func() (*session.Repository, error) + TargetRepoFactory func() (*target.Repository, error) ) diff --git a/internal/servers/controller/controller.go b/internal/servers/controller/controller.go index 93abca945..4541b6108 100644 --- a/internal/servers/controller/controller.go +++ b/internal/servers/controller/controller.go @@ -10,6 +10,7 @@ import ( "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/cmd/config" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" @@ -41,14 +42,15 @@ type Controller struct { workerStatusUpdateTimes *sync.Map // Repo factory methods - AuthTokenRepoFn common.AuthTokenRepoFactory - IamRepoFn common.IamRepoFactory - OidcRepoFn common.OidcAuthRepoFactory - PasswordAuthRepoFn common.PasswordAuthRepoFactory - ServersRepoFn common.ServersRepoFactory - SessionRepoFn common.SessionRepoFactory - StaticHostRepoFn common.StaticRepoFactory - TargetRepoFn common.TargetRepoFactory + AuthTokenRepoFn common.AuthTokenRepoFactory + VaultCredentialRepoFn common.VaultCredentialRepoFactory + IamRepoFn common.IamRepoFactory + OidcRepoFn common.OidcAuthRepoFactory + PasswordAuthRepoFn common.PasswordAuthRepoFactory + ServersRepoFn common.ServersRepoFactory + SessionRepoFn common.SessionRepoFactory + StaticHostRepoFn common.StaticRepoFactory + TargetRepoFn common.TargetRepoFactory scheduler *scheduler.Scheduler @@ -112,7 +114,6 @@ func New(conf *Config) (*Controller, error) { ); err != nil { return nil, fmt.Errorf("error adding config keys to kms: %w", err) } - jobRepoFn := func() (*job.Repository, error) { return job.NewRepository(dbase, dbase, c.kms) } @@ -131,6 +132,9 @@ func New(conf *Config) (*Controller, error) { authtoken.WithTokenTimeToLiveDuration(c.conf.RawConfig.Controller.AuthTokenTimeToLiveDuration), authtoken.WithTokenTimeToStaleDuration(c.conf.RawConfig.Controller.AuthTokenTimeToStaleDuration)) } + c.VaultCredentialRepoFn = func() (*vault.Repository, error) { + return vault.NewRepository(dbase, dbase, c.kms, c.scheduler) + } c.ServersRepoFn = func() (*servers.Repository, error) { return servers.NewRepository(dbase, dbase, c.kms) } @@ -163,7 +167,6 @@ func (c *Controller) Start() error { if err := c.scheduler.Start(c.baseContext); err != nil { return fmt.Errorf("error starting scheduler: %w", err) } - if err := c.startListeners(); err != nil { return fmt.Errorf("error starting controller listeners: %w", err) } @@ -178,7 +181,8 @@ func (c *Controller) Start() error { } func (c *Controller) registerJobs() error { - return nil + rw := db.New(c.conf.Database) + return vault.RegisterJobs(c.baseContext, c.scheduler, rw, rw, c.kms, c.logger) } func (c *Controller) Shutdown(serversOnly bool) error { diff --git a/internal/servers/controller/handler.go b/internal/servers/controller/handler.go index 0101f37e1..fa91c0555 100644 --- a/internal/servers/controller/handler.go +++ b/internal/servers/controller/handler.go @@ -20,6 +20,8 @@ import ( "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/servers/controller/handlers/accounts" "github.com/hashicorp/boundary/internal/servers/controller/handlers/authmethods" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentiallibraries" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentialstores" "github.com/hashicorp/boundary/internal/servers/controller/handlers/host_sets" "github.com/hashicorp/boundary/internal/servers/controller/handlers/managed_groups" "github.com/hashicorp/boundary/internal/servers/controller/handlers/sessions" @@ -139,7 +141,8 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er c.IamRepoFn, c.ServersRepoFn, c.SessionRepoFn, - c.StaticHostRepoFn) + c.StaticHostRepoFn, + c.VaultCredentialRepoFn) if err != nil { return nil, fmt.Errorf("failed to create target handler service: %w", err) } @@ -174,6 +177,20 @@ func handleGrpcGateway(c *Controller, props HandlerProperties) (http.Handler, er if err := services.RegisterManagedGroupServiceHandlerServer(ctx, mux, mgs); err != nil { return nil, fmt.Errorf("failed to register managed groups service handler: %w", err) } + cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create credential store handler service: %w", err) + } + if err := services.RegisterCredentialStoreServiceHandlerServer(ctx, mux, cs); err != nil { + return nil, fmt.Errorf("failed to register credential store service handler: %w", err) + } + cl, err := credentiallibraries.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) + if err != nil { + return nil, fmt.Errorf("failed to create credential library handler service: %w", err) + } + if err := services.RegisterCredentialLibraryServiceHandlerServer(ctx, mux, cl); err != nil { + return nil, fmt.Errorf("failed to register credential library service handler: %w", err) + } return mux, nil } diff --git a/internal/servers/controller/handler_test.go b/internal/servers/controller/handler_test.go index 8ed729817..d915e9f27 100644 --- a/internal/servers/controller/handler_test.go +++ b/internal/servers/controller/handler_test.go @@ -106,6 +106,8 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/auth-methods/someid:authenticate:callback", "v1/auth-tokens", "v1/auth-tokens/someid", + "v1/credential-stores", + "v1/credential-stores/someid", "v1/groups", "v1/groups/someid", "v1/host-catalogs", @@ -130,6 +132,7 @@ func TestHandleImplementedPaths(t *testing.T) { // Creation end points "v1/accounts", "v1/auth-methods", + "v1/credential-stores", "v1/groups", "v1/host-catalogs", "v1/host-sets", @@ -169,6 +172,7 @@ func TestHandleImplementedPaths(t *testing.T) { "v1/accounts/someid", "v1/auth-methods/someid", "v1/auth-tokens/someid", + "v1/credential-stores/someid", "v1/groups/someid", "v1/host-catalogs/someid", "v1/host-sets/someid", @@ -181,6 +185,7 @@ func TestHandleImplementedPaths(t *testing.T) { "PATCH": { "v1/accounts/someid", "v1/auth-methods/someid", + "v1/credential-stores/someid", "v1/groups/someid", "v1/host-catalogs/someid", "v1/host-sets/someid", diff --git a/internal/servers/controller/handlers/accounts/account_service.go b/internal/servers/controller/handlers/accounts/account_service.go index e1fae10bd..aacc07973 100644 --- a/internal/servers/controller/handlers/accounts/account_service.go +++ b/internal/servers/controller/handlers/accounts/account_service.go @@ -80,10 +80,10 @@ var ( func init() { var err error - if pwMaskManager, err = handlers.NewMaskManager(&pwstore.Account{}, &pb.Account{}, &pb.PasswordAccountAttributes{}); err != nil { + if pwMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&pwstore.Account{}}, handlers.MaskSource{&pb.Account{}, &pb.PasswordAccountAttributes{}}); err != nil { panic(err) } - if oidcMaskManager, err = handlers.NewMaskManager(&oidcstore.Account{}, &pb.Account{}, &pb.OidcAccountAttributes{}); err != nil { + if oidcMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&oidcstore.Account{}}, handlers.MaskSource{&pb.Account{}, &pb.OidcAccountAttributes{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/authmethods/oidc.go b/internal/servers/controller/handlers/authmethods/oidc.go index 6b88ccdd2..e934cc93f 100644 --- a/internal/servers/controller/handlers/authmethods/oidc.go +++ b/internal/servers/controller/handlers/authmethods/oidc.go @@ -49,7 +49,7 @@ var oidcMaskManager handlers.MaskManager func init() { var err error - if oidcMaskManager, err = handlers.NewMaskManager(&oidcstore.AuthMethod{}, &pb.AuthMethod{}, &pb.OidcAuthMethodAttributes{}); err != nil { + if oidcMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&oidcstore.AuthMethod{}}, handlers.MaskSource{&pb.AuthMethod{}, &pb.OidcAuthMethodAttributes{}}); err != nil { panic(err) } diff --git a/internal/servers/controller/handlers/authmethods/oidc_test.go b/internal/servers/controller/handlers/authmethods/oidc_test.go index ffa5e1718..88c79a8d3 100644 --- a/internal/servers/controller/handlers/authmethods/oidc_test.go +++ b/internal/servers/controller/handlers/authmethods/oidc_test.go @@ -148,9 +148,6 @@ func TestList_FilterNonPublic(t *testing.T) { atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(rw, rw, kmsCache) } - authTokenRepoFn := func() (*authtoken.Repository, error) { - return authtoken.NewRepository(rw, rw, kmsCache) - } serversRepoFn := func() (*servers.Repository, error) { return servers.NewRepository(rw, rw, kmsCache) } @@ -206,7 +203,7 @@ func TestList_FilterNonPublic(t *testing.T) { return auth.NewVerifierContext(requests.NewRequestContext(context.Background()), nil, iamRepoFn, - authTokenRepoFn, + atRepoFn, serversRepoFn, kmsCache, auth.RequestInfo{ diff --git a/internal/servers/controller/handlers/authmethods/password.go b/internal/servers/controller/handlers/authmethods/password.go index 79390e59e..5b41f0f51 100644 --- a/internal/servers/controller/handlers/authmethods/password.go +++ b/internal/servers/controller/handlers/authmethods/password.go @@ -28,7 +28,7 @@ var pwMaskManager handlers.MaskManager func init() { var err error - if pwMaskManager, err = handlers.NewMaskManager(&pwstore.AuthMethod{}, &pb.AuthMethod{}, &pb.PasswordAuthMethodAttributes{}); err != nil { + if pwMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&pwstore.AuthMethod{}}, handlers.MaskSource{&pb.AuthMethod{}, &pb.PasswordAuthMethodAttributes{}}); err != nil { panic(err) } diff --git a/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service.go b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service.go new file mode 100644 index 000000000..e2a0a1c5d --- /dev/null +++ b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service.go @@ -0,0 +1,602 @@ +package credentiallibraries + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/errors" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/requests" + "github.com/hashicorp/boundary/internal/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/hashicorp/boundary/internal/types/action" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/sdk/strutil" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const ( + vaultPathField = "attributes.path" + httpMethodField = "attributes.http_method" + httpRequestBodyField = "attributes.http_request_body" +) + +var ( + maskManager handlers.MaskManager + + // IdActions contains the set of actions that can be performed on + // individual resources + IdActions = action.ActionSet{ + action.NoOp, + action.Read, + action.Update, + action.Delete, + } + + // CollectionActions contains the set of actions that can be performed on + // this collection + CollectionActions = action.ActionSet{ + action.Create, + action.List, + } +) + +func init() { + var err error + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.CredentialLibrary{}}, + handlers.MaskSource{&pb.CredentialLibrary{}, &pb.VaultCredentialLibraryAttributes{}}); err != nil { + panic(err) + } +} + +// Service handles request as described by the pbs.CredentialLibraryServiceServer interface. +type Service struct { + pbs.UnimplementedCredentialLibraryServiceServer + + iamRepoFn common.IamRepoFactory + repoFn common.VaultCredentialRepoFactory +} + +// NewService returns a credential library service which handles credential library related requests to boundary. +func NewService(repo common.VaultCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) { + const op = "credentiallibraries.NewService" + if iamRepo == nil { + return Service{}, errors.New(errors.InvalidParameter, op, "missing iam repository") + } + if repo == nil { + return Service{}, errors.New(errors.InvalidParameter, op, "missing vault credential repository") + } + return Service{iamRepoFn: iamRepo, repoFn: repo}, nil +} + +var _ pbs.CredentialLibraryServiceServer = Service{} + +// ListCredentialLibraries implements the interface pbs.CredentialLibraryServiceServer +func (s Service) ListCredentialLibraries(ctx context.Context, req *pbs.ListCredentialLibrariesRequest) (*pbs.ListCredentialLibrariesResponse, error) { + if err := validateListRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetCredentialStoreId(), action.List) + if authResults.Error != nil { + return nil, authResults.Error + } + + csl, err := s.listFromRepo(ctx, req.GetCredentialStoreId()) + if err != nil { + return nil, err + } + if len(csl) == 0 { + return &pbs.ListCredentialLibrariesResponse{}, nil + } + + filter, err := handlers.NewFilter(req.GetFilter()) + if err != nil { + return nil, err + } + finalItems := make([]*pb.CredentialLibrary, 0, len(csl)) + res := perms.Resource{ + ScopeId: authResults.Scope.Id, + Type: resource.CredentialLibrary, + Pin: req.GetCredentialStoreId(), + } + for _, item := range csl { + res.Id = item.GetPublicId() + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() + if len(authorizedActions) == 0 { + continue + } + + outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId) + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } + + item, err := toProto(item, outputOpts...) + if err != nil { + return nil, err + } + + if filter.Match(item) { + finalItems = append(finalItems, item) + } + } + return &pbs.ListCredentialLibrariesResponse{Items: finalItems}, nil +} + +// GetCredentialLibrary implements the interface pbs.CredentialLibraryServiceServer. +func (s Service) GetCredentialLibrary(ctx context.Context, req *pbs.GetCredentialLibraryRequest) (*pbs.GetCredentialLibraryResponse, error) { + const op = "credentiallibraries.(Service).GetCredentialLibrary" + + if err := validateGetRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Read) + if authResults.Error != nil { + return nil, authResults.Error + } + cs, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(cs, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.GetCredentialLibraryResponse{Item: item}, nil +} + +// CreateCredentialLibrary implements the interface pbs.CredentialLibraryServiceServer. +func (s Service) CreateCredentialLibrary(ctx context.Context, req *pbs.CreateCredentialLibraryRequest) (*pbs.CreateCredentialLibraryResponse, error) { + const op = "credentiallibraries.(Service).CreateCredentialLibrary" + + if err := validateCreateRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetItem().GetCredentialStoreId(), action.Create) + if authResults.Error != nil { + return nil, authResults.Error + } + cl, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cl.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(cl, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.CreateCredentialLibraryResponse{ + Item: item, + Uri: fmt.Sprintf("credential-libraries/%s", item.GetId()), + }, nil +} + +// UpdateCredentialLibrary implements the interface pbs.CredentialLibraryServiceServer. +func (s Service) UpdateCredentialLibrary(ctx context.Context, req *pbs.UpdateCredentialLibraryRequest) (*pbs.UpdateCredentialLibraryResponse, error) { + const op = "credentiallibraries.(Service).UpdateCredentialLibrary" + + if err := validateUpdateRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Update) + if authResults.Error != nil { + return nil, authResults.Error + } + cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cl.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(cl, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.UpdateCredentialLibraryResponse{Item: item}, nil +} + +// DeleteCredentialLibrary implements the interface pbs.CredentialLibraryServiceServer. +func (s Service) DeleteCredentialLibrary(ctx context.Context, req *pbs.DeleteCredentialLibraryRequest) (*pbs.DeleteCredentialLibraryResponse, error) { + if err := validateDeleteRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Delete) + if authResults.Error != nil { + return nil, authResults.Error + } + _, err := s.deleteFromRepo(ctx, authResults.Scope.GetId(), req.GetId()) + if err != nil { + return nil, err + } + return nil, nil +} + +func (s Service) listFromRepo(ctx context.Context, storeId string) ([]*vault.CredentialLibrary, error) { + const op = "credentiallibraries.(Service).listFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + csl, err := repo.ListCredentialLibraries(ctx, storeId) + if err != nil { + return nil, errors.Wrap(err, op) + } + return csl, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (credential.Library, error) { + const op = "credentiallibraries.(Service).getFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + cs, err := repo.LookupCredentialLibrary(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return nil, errors.Wrap(err, op) + } + if cs == nil { + return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("credential library %q not found", id)) + } + return cs, err +} + +func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.CredentialLibrary) (credential.Library, error) { + const op = "credentiallibraries.(Service).createInRepo" + cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item) + if err != nil { + return nil, errors.Wrap(err, op) + } + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + out, err := repo.CreateCredentialLibrary(ctx, scopeId, cl) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create credential library")) + } + if out == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential library but no error returned from repository.") + } + return out, nil +} + +func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.CredentialLibrary) (credential.Library, error) { + const op = "credentiallibraries.(Service).updateInRepo" + cl, err := toStorageVaultLibrary(item.GetCredentialStoreId(), item) + if err != nil { + return nil, errors.Wrap(err, op) + } + cl.PublicId = id + + dbMask := maskManager.Translate(mask) + if len(dbMask) == 0 { + return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) + } + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + out, rowsUpdated, err := repo.UpdateCredentialLibrary(ctx, projId, cl, item.GetVersion(), dbMask) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to update credential library")) + } + if rowsUpdated == 0 { + return nil, handlers.NotFoundErrorf("Credential Library %q doesn't exist or incorrect version provided.", id) + } + return out, nil +} + +func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { + const op = "credentiallibraries.(Service).deleteFromRepo" + repo, err := s.repoFn() + if err != nil { + return false, err + } + rows, err := repo.DeleteCredentialLibrary(ctx, scopeId, id) + if err != nil { + if errors.IsNotFoundError(err) { + return false, nil + } + return false, errors.Wrap(err, op, errors.WithMsg("unable to delete credential library")) + } + return rows > 0, nil +} + +func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults { + const op = "credentiallibraries.(Service).authResult" + res := auth.VerifyResults{} + repo, err := s.repoFn() + if err != nil { + res.Error = err + return res + } + + var parentId string + opts := []auth.Option{auth.WithType(resource.CredentialLibrary), auth.WithAction(a)} + switch a { + case action.List, action.Create: + parentId = id + default: + opts = append(opts, auth.WithId(id)) + + switch credential.SubtypeFromId(id) { + case credential.VaultSubtype: + cl, err := repo.LookupCredentialLibrary(ctx, id) + if err != nil { + res.Error = err + return res + } + if cl == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cl.GetStoreId() + default: + res.Error = errors.New(errors.InvalidParameter, op, "unrecognized credential library subtype from id") + return res + } + } + + if parentId == "" { + res.Error = errors.New(errors.RecordNotFound, op, "unable to find credential store for provided library") + return res + } + + opts = append(opts, auth.WithPin(parentId)) + + switch credential.SubtypeFromId(parentId) { + case credential.VaultSubtype: + cs, err := repo.LookupCredentialStore(ctx, parentId) + if err != nil { + res.Error = err + return res + } + if cs == nil { + res.Error = handlers.NotFoundError() + return res + } + opts = append(opts, auth.WithScopeId(cs.GetScopeId())) + default: + res.Error = errors.New(errors.InvalidParameter, op, "unrecognized credential store subtype from id") + return res + } + + return auth.Verify(ctx, opts...) +} + +func toProto(in credential.Library, opt ...handlers.Option) (*pb.CredentialLibrary, error) { + const op = "credentiallibraries.toProto" + + opts := handlers.GetOpts(opt...) + if opts.WithOutputFields == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building credential library proto") + } + outputFields := *opts.WithOutputFields + + out := pb.CredentialLibrary{} + if outputFields.Has(globals.IdField) { + out.Id = in.GetPublicId() + } + if outputFields.Has(globals.CredentialStoreIdField) { + out.CredentialStoreId = in.GetStoreId() + } + if outputFields.Has(globals.TypeField) { + out.Type = credential.SubtypeFromId(in.GetPublicId()).String() + } + if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { + out.Description = wrapperspb.String(in.GetDescription()) + } + if outputFields.Has(globals.NameField) && in.GetName() != "" { + out.Name = wrapperspb.String(in.GetName()) + } + if outputFields.Has(globals.CreatedTimeField) { + out.CreatedTime = in.GetCreateTime().GetTimestamp() + } + if outputFields.Has(globals.UpdatedTimeField) { + out.UpdatedTime = in.GetUpdateTime().GetTimestamp() + } + if outputFields.Has(globals.VersionField) { + out.Version = in.GetVersion() + } + if outputFields.Has(globals.ScopeField) { + out.Scope = opts.WithScope + } + if outputFields.Has(globals.AuthorizedActionsField) { + out.AuthorizedActions = opts.WithAuthorizedActions + } + if outputFields.Has(globals.AttributesField) { + switch credential.SubtypeFromId(in.GetPublicId()) { + case credential.VaultSubtype: + vaultIn, ok := in.(*vault.CredentialLibrary) + if !ok { + return nil, errors.New(errors.Internal, op, "unable to cast to vault credential library") + } + attrs := &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(vaultIn.GetVaultPath()), + } + if vaultIn.GetHttpMethod() != "" { + attrs.HttpMethod = wrapperspb.String(vaultIn.GetHttpMethod()) + } + if vaultIn.GetHttpRequestBody() != nil { + attrs.HttpRequestBody = wrapperspb.String(string(vaultIn.GetHttpRequestBody())) + } + var err error + out.Attributes, err = handlers.ProtoToStruct(attrs) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("failed to convert resource from storage to api")) + } + } + } + return &out, nil +} + +func toStorageVaultLibrary(storeId string, in *pb.CredentialLibrary) (out *vault.CredentialLibrary, err error) { + const op = "credentiallibraries.toStorageVaultLibrary" + var opts []vault.Option + if in.GetName() != nil { + opts = append(opts, vault.WithName(in.GetName().GetValue())) + } + if in.GetDescription() != nil { + opts = append(opts, vault.WithDescription(in.GetDescription().GetValue())) + } + + attrs := &pb.VaultCredentialLibraryAttributes{} + if err := handlers.StructToProto(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to parse the attributes")) + } + + if attrs.GetHttpMethod() != nil { + opts = append(opts, vault.WithMethod(vault.Method(strings.ToUpper(attrs.GetHttpMethod().GetValue())))) + } + if attrs.GetHttpRequestBody() != nil { + opts = append(opts, vault.WithRequestBody([]byte(attrs.GetHttpRequestBody().GetValue()))) + } + + cs, err := vault.NewCredentialLibrary(storeId, attrs.GetPath().GetValue(), opts...) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to build credential library")) + } + return cs, err +} + +// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce +// requirements on the structure of the request. They verify that: +// * The path passed in is correctly formatted +// * All required parameters are set +// * There are no conflicting parameters provided +func validateGetRequest(req *pbs.GetCredentialLibraryRequest) error { + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialLibraryPrefix) +} + +func validateCreateRequest(req *pbs.CreateCredentialLibraryRequest) error { + return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { + badFields := map[string]string{} + switch credential.SubtypeFromId(req.GetItem().GetCredentialStoreId()) { + case credential.VaultSubtype: + if t := req.GetItem().GetType(); t != "" && credential.SubtypeFromType(t) != credential.VaultSubtype { + badFields[globals.CredentialStoreIdField] = "If included, type must match that of the credential store." + } + attrs := &pb.VaultCredentialLibraryAttributes{} + if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." + break + } + if attrs.GetPath().GetValue() == "" { + badFields[vaultPathField] = "This is a required field." + } + if m := attrs.GetHttpMethod(); m != nil && !strutil.StrListContains([]string{"GET", "POST"}, strings.ToUpper(m.GetValue())) { + badFields[httpMethodField] = "If set, value must be 'GET' or 'POST'." + } + if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) != "POST" { + badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField) + } + default: + badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id." + } + return badFields + }) +} + +func validateUpdateRequest(req *pbs.UpdateCredentialLibraryRequest) error { + return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { + badFields := map[string]string{} + switch credential.SubtypeFromId(req.GetId()) { + case credential.VaultSubtype: + if req.GetItem().GetType() != "" && credential.SubtypeFromType(req.GetItem().GetType()) != credential.VaultSubtype { + badFields[globals.TypeField] = "Cannot modify resource type." + } + attrs := &pb.VaultCredentialLibraryAttributes{} + if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." + break + } + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), vaultPathField) && attrs.GetPath().GetValue() == "" { + badFields[vaultPathField] = "This is a required field and cannot be set to empty." + } + if m := attrs.GetHttpMethod(); handlers.MaskContains(req.GetUpdateMask().GetPaths(), httpMethodField) && m != nil && !strutil.StrListContains([]string{"GET", "POST"}, strings.ToUpper(m.GetValue())) { + badFields[httpMethodField] = "If set, value must be 'GET' or 'POST'." + } + if b := attrs.GetHttpRequestBody(); b != nil && strings.ToUpper(attrs.GetHttpMethod().GetValue()) == "GET" { + badFields[httpRequestBodyField] = fmt.Sprintf("Field can only be set if %q is set to the value 'POST'.", httpMethodField) + } + } + return badFields + }, vault.CredentialLibraryPrefix) +} + +func validateDeleteRequest(req *pbs.DeleteCredentialLibraryRequest) error { + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialLibraryPrefix) +} + +func validateListRequest(req *pbs.ListCredentialLibrariesRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetCredentialStoreId()), vault.CredentialStorePrefix) { + badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id." + } + if _, err := handlers.NewFilter(req.GetFilter()); err != nil { + badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} diff --git a/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go new file mode 100644 index 000000000..57e2d4a8b --- /dev/null +++ b/internal/servers/controller/handlers/credentiallibraries/credentiallibrary_service_test.go @@ -0,0 +1,881 @@ +package credentiallibraries + +import ( + "fmt" + "sort" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries" + scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +var testAuthorizedActions = []string{"no-op", "read", "update", "delete"} + +func TestList(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prjNoLibs := iam.TestScopes(t, iamRepo) + storeNoLibs := vault.TestCredentialStores(t, conn, wrapper, prjNoLibs.GetPublicId(), 1)[0] + _, prj := iam.TestScopes(t, iamRepo) + + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + var wantLibraries []*pb.CredentialLibrary + for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) { + wantLibraries = append(wantLibraries, &pb.CredentialLibrary{ + Id: l.GetPublicId(), + CredentialStoreId: l.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: l.GetCreateTime().GetTimestamp(), + UpdatedTime: l.GetUpdateTime().GetTimestamp(), + Version: l.GetVersion(), + Type: credential.VaultSubtype.String(), + AuthorizedActions: testAuthorizedActions, + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(l.GetVaultPath()), + HttpMethod: wrapperspb.String(l.GetHttpMethod()), + }) + require.NoError(t, err) + return attrs + }(), + }) + } + + cases := []struct { + name string + req *pbs.ListCredentialLibrariesRequest + res *pbs.ListCredentialLibrariesResponse + err error + }{ + { + name: "List Many Libraries", + req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId()}, + res: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries}, + }, + { + name: "List No Libraries", + req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeNoLibs.GetPublicId()}, + res: &pbs.ListCredentialLibrariesResponse{}, + }, + { + name: "Filter to One Libraries", + req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantLibraries[1].GetId())}, + res: &pbs.ListCredentialLibrariesResponse{Items: wantLibraries[1:2]}, + }, + { + name: "Filter to No Libraries", + req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, + res: &pbs.ListCredentialLibrariesResponse{}, + }, + { + name: "Filter Bad Format", + req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: store.GetPublicId(), Filter: `"//id/"=="bad"`}, + err: handlers.InvalidArgumentErrorf("bad format", nil), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err, "Couldn't create new host set service.") + + // Test non-anonymous listing + got, gErr := s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err), "ListCredentialLibrary(%q) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(t, gErr) + sort.Slice(got.Items, func(i, j int) bool { + return got.Items[i].GetId() < got.Items[j].GetId() + }) + want := proto.Clone(tc.res).(*pbs.ListCredentialLibrariesResponse) + sort.Slice(want.Items, func(i, j int) bool { + return want.Items[i].GetId() < want.Items[j].GetId() + }) + assert.Empty(t, cmp.Diff(got, want, protocmp.Transform())) + + // Test anonymous listing + got, gErr = s.ListCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) + require.NoError(t, gErr) + assert.Len(t, got.Items, len(tc.res.Items)) + for _, item := range got.GetItems() { + require.Nil(t, item.CreatedTime) + require.Nil(t, item.UpdatedTime) + require.Zero(t, item.Version) + } + }) + } +} + +func TestCreate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + defaultCL := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] + defaultCreated := defaultCL.GetCreateTime().GetTimestamp() + + cases := []struct { + name string + req *pbs.CreateCredentialLibraryRequest + res *pbs.CreateCredentialLibraryResponse + idPrefix string + err error + wantErr bool + }{ + { + name: "missing vault path", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{}) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialLibraryPrefix + "_", + wantErr: true, + }, + { + name: "Can't specify Id", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Id: vault.CredentialLibraryPrefix + "_notallowed", + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + CreatedTime: timestamppb.Now(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + UpdatedTime: timestamppb.Now(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Type and parent id must match", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Type: "static", + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "parent id must be included", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Attributes must be valid for vault type", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + attrs.Fields["invalid"] = structpb.NewStringValue("foo") + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Cannot specify a http request body when http method is get", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpRequestBody: wrapperspb.String("foo"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid Method", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("PATCH"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Request Body With GET Method", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpRequestBody: wrapperspb.String("foo"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Using POST method", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("post"), + HttpRequestBody: wrapperspb.String("foo"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialLibraryPrefix + "_", + res: &pbs.CreateCredentialLibraryResponse{ + Uri: fmt.Sprintf("credential-libraries/%s_", vault.CredentialLibraryPrefix), + Item: &pb.CredentialLibrary{ + Id: store.GetPublicId(), + CredentialStoreId: store.GetPublicId(), + CreatedTime: store.GetCreateTime().GetTimestamp(), + UpdatedTime: store.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("POST"), + HttpRequestBody: wrapperspb.String("foo"), + }) + require.NoError(t, err) + return attrs + }(), + AuthorizedActions: testAuthorizedActions, + }, + }, + }, + { + name: "Create a valid vault CredentialLibrary", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialLibraryPrefix + "_", + res: &pbs.CreateCredentialLibraryResponse{ + Uri: fmt.Sprintf("credential-libraries/%s_", vault.CredentialLibraryPrefix), + Item: &pb.CredentialLibrary{ + Id: store.GetPublicId(), + CredentialStoreId: store.GetPublicId(), + CreatedTime: store.GetCreateTime().GetTimestamp(), + UpdatedTime: store.GetUpdateTime().GetTimestamp(), + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("GET"), + }) + require.NoError(t, err) + return attrs + }(), + AuthorizedActions: testAuthorizedActions, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + s, err := NewService(repoFn, iamRepoFn) + require.NoError(err, "Error when getting new credential store service.") + + got, gErr := s.CreateCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), tc.req) + if tc.wantErr || tc.err != nil { + require.Error(gErr) + if tc.err != nil { + assert.True(errors.Is(gErr, tc.err), "CreateCredentialLibrary(...) got error %v, wanted %v", gErr, tc.err) + } + return + } + require.NoError(gErr) + if tc.res == nil { + require.Nil(got) + } + if got != nil { + assert.Contains(got.GetUri(), tc.res.Uri) + assert.True(strings.HasPrefix(got.GetItem().GetId(), tc.idPrefix)) + gotCreateTime := got.GetItem().GetCreatedTime() + gotUpdateTime := got.GetItem().GetUpdatedTime() + + // Verify it is a credential store created after the test setup's default credential store + assert.True(gotCreateTime.AsTime().After(defaultCreated.AsTime()), "New credential store should have been created after default credential store. Was created %v, which is after %v", gotCreateTime, defaultCreated) + assert.True(gotUpdateTime.AsTime().After(defaultCreated.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated) + + // Clear all values which are hard to compare against. + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "CreateCredentialLibrary(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestGet(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + + cases := []struct { + name string + id string + res *pbs.GetCredentialLibraryResponse + err error + }{ + { + name: "success", + id: vl.GetPublicId(), + res: &pbs.GetCredentialLibraryResponse{ + Item: &pb.CredentialLibrary{ + Id: vl.GetPublicId(), + CredentialStoreId: vl.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: credential.VaultSubtype.String(), + AuthorizedActions: testAuthorizedActions, + CreatedTime: vl.CreateTime.GetTimestamp(), + UpdatedTime: vl.UpdateTime.GetTimestamp(), + Version: 1, + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(vl.GetVaultPath()), + HttpMethod: wrapperspb.String(vl.GetHttpMethod()), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", vault.CredentialLibraryPrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.HostPrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.GetCredentialLibraryRequest{Id: tc.id} + // Test non-anonymous get + got, gErr := s.GetCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), req) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err)) + return + } + require.NoError(t, gErr) + assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform())) + + // Test anonymous get + got, gErr = s.GetCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), req) + require.NoError(t, gErr) + require.Nil(t, got.Item.CreatedTime) + require.Nil(t, got.Item.UpdatedTime) + require.Zero(t, got.Item.Version) + }) + } +} + +func TestDelete(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + + cases := []struct { + name string + id string + err error + }{ + { + name: "success", + id: vl.GetPublicId(), + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", vault.CredentialLibraryPrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.HostPrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, gErr := s.DeleteCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialLibraryRequest{Id: tc.id}) + assert.Nil(t, got) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err)) + return + } + require.NoError(t, gErr) + g, err := s.GetCredentialLibrary(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.GetCredentialLibraryRequest{Id: tc.id}) + assert.Nil(t, g) + assert.True(t, errors.Is(err, handlers.NotFoundError())) + }) + } +} + +func TestUpdate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) + + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + store, diffStore := cs[0], cs[1] + + freshLibrary := func() (*vault.CredentialLibrary, func()) { + vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] + clean := func() { + _, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()}) + require.NoError(t, err) + } + return vl, clean + } + + fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { + return &fieldmaskpb.FieldMask{Paths: paths} + } + + v := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS)) + _, token := v.CreateToken(t) + _ = token + + successCases := []struct { + name string + req *pbs.UpdateCredentialLibraryRequest + res func(*pb.CredentialLibrary) *pb.CredentialLibrary + }{ + { + name: "name and description", + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask("name", "description"), + Item: &pb.CredentialLibrary{ + Name: wrapperspb.String("basic"), + Description: wrapperspb.String("basic"), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.Name = wrapperspb.String("basic") + out.Description = wrapperspb.String("basic") + return out + }, + }, + { + name: "update method", + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(httpMethodField), + Item: &pb.CredentialLibrary{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + HttpMethod: wrapperspb.String("pOsT"), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path0") + out.Attributes.Fields["http_method"] = structpb.NewStringValue("POST") + return out + }, + }, + { + name: "update request body and method", + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(httpRequestBodyField, httpMethodField), + Item: &pb.CredentialLibrary{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + HttpMethod: wrapperspb.String("pOsT"), + HttpRequestBody: wrapperspb.String("body"), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.Attributes.Fields["path"] = structpb.NewStringValue("vault/path0") + out.Attributes.Fields["http_method"] = structpb.NewStringValue("POST") + out.Attributes.Fields["http_request_body"] = structpb.NewStringValue("body") + return out + }, + }, + { + name: "update path", + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(vaultPathField), + Item: &pb.CredentialLibrary{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.Attributes.Fields["path"] = structpb.NewStringValue("something") + return out + }, + }, + } + + for _, tc := range successCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + st, cleanup := freshLibrary() + defer cleanup() + + if tc.req.Item.GetVersion() == 0 { + tc.req.Item.Version = 1 + } + if tc.req.GetId() == "" { + tc.req.Id = st.GetPublicId() + } + resToChange, err := s.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()}) + require.NoError(err) + want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())} + + got, gErr := s.UpdateCredentialLibrary(ctx, tc.req) + require.NoError(gErr) + require.NotNil(got) + + gotUpdateTime := got.GetItem().GetUpdatedTime() + created := st.GetCreateTime().GetTimestamp() + assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created) + + want.Item.UpdatedTime = got.Item.UpdatedTime + + assert.EqualValues(2, got.Item.Version) + want.Item.Version = 2 + + assert.Empty(cmp.Diff(got, want, protocmp.Transform())) + }) + } + + vl, cleanup := freshLibrary() + defer cleanup() + + errCases := []struct { + name string + path string + item *pb.CredentialLibrary + }{ + { + name: "read only type", + path: "type", + item: &pb.CredentialLibrary{Type: "something"}, + }, + { + name: "read only store_id", + path: "store_id", + item: &pb.CredentialLibrary{CredentialStoreId: diffStore.GetPublicId()}, + }, + { + name: "read only updated_time", + path: "updated_time", + item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()}, + }, + { + name: "read only created_time", + path: "created_time", + item: &pb.CredentialLibrary{UpdatedTime: timestamppb.Now()}, + }, + { + name: "read only authorized actions", + path: "authorized actions", + item: &pb.CredentialLibrary{AuthorizedActions: append(testAuthorizedActions, "another")}, + }, + } + for _, tc := range errCases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + Item: tc.item, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}}, + } + req.Item.Version = vl.Version + + got, gErr := s.UpdateCredentialLibrary(ctx, req) + assert.Error(t, gErr) + assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr) + assert.Nil(t, got) + }) + } + + t.Run("request body and method interactions", func(t *testing.T) { + vl, cleanup := freshLibrary() + defer cleanup() + require.Equal(t, "GET", vl.GetHttpMethod()) + + // Cannot set request body on GET request + cl, err := s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpRequestBodyField}}, + Item: &pb.CredentialLibrary{ + Version: vl.GetVersion(), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "http_request_body": structpb.NewStringValue("body"), + }, + }, + }, + }) + require.Error(t, err) + require.Nil(t, cl) + + // Can set POST when there is no request body + cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField}}, + Item: &pb.CredentialLibrary{ + Version: vl.GetVersion(), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "http_method": structpb.NewStringValue("POST"), + }, + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, cl) + require.Equal(t, "POST", cl.GetItem().GetAttributes().GetFields()["http_method"].GetStringValue()) + + // Can set request body on POST + cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpRequestBodyField}}, + Item: &pb.CredentialLibrary{ + Version: cl.Item.GetVersion(), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "http_request_body": structpb.NewStringValue("body"), + }, + }, + }, + }) + require.NoError(t, err) + require.NotNil(t, cl) + require.Equal(t, "POST", cl.GetItem().GetAttributes().GetFields()["http_method"].GetStringValue()) + require.Equal(t, "body", cl.GetItem().GetAttributes().GetFields()["http_request_body"].GetStringValue()) + + // Cannot unset POST method (defaulting to GET) when request body is set + _, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField}}, + Item: &pb.CredentialLibrary{ + Version: cl.GetItem().GetVersion(), + }, + }) + require.Error(t, err) + + // Can clear request body and method (defaulting to GET) in the same request + cl, err = s.UpdateCredentialLibrary(ctx, &pbs.UpdateCredentialLibraryRequest{ + Id: vl.GetPublicId(), + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{httpMethodField, httpRequestBodyField}}, + Item: &pb.CredentialLibrary{ + Version: cl.GetItem().GetVersion(), + }, + }) + assert.NoError(t, err) + require.NotNil(t, cl) + assert.Equal(t, "GET", cl.GetItem().GetAttributes().GetFields()["http_method"].GetStringValue()) + assert.Nil(t, cl.GetItem().GetAttributes().GetFields()["http_request_body"]) + }) +} diff --git a/internal/servers/controller/handlers/credentialstores/credentialstore_service.go b/internal/servers/controller/handlers/credentialstores/credentialstore_service.go new file mode 100644 index 000000000..b58f3765b --- /dev/null +++ b/internal/servers/controller/handlers/credentialstores/credentialstore_service.go @@ -0,0 +1,712 @@ +package credentialstores + +import ( + "context" + "encoding/base64" + "encoding/pem" + "fmt" + + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/credential/vault/store" + "github.com/hashicorp/boundary/internal/errors" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/perms" + "github.com/hashicorp/boundary/internal/requests" + "github.com/hashicorp/boundary/internal/servers/controller/common" + "github.com/hashicorp/boundary/internal/servers/controller/common/scopeids" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentiallibraries" + "github.com/hashicorp/boundary/internal/types/action" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/internal/types/scope" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const ( + addressField = "attributes.address" + vaultTokenField = "attributes.token" + vaultTokenHmacField = "attributes.token_hmac" + caCertsField = "attributes.ca_cert" + clientCertField = "attributes.client_certificate" + clientCertKeyField = "attributes.certificate_key" +) + +var ( + maskManager handlers.MaskManager + + // IdActions contains the set of actions that can be performed on + // individual resources + IdActions = action.ActionSet{ + action.NoOp, + action.Read, + action.Update, + action.Delete, + } + + // CollectionActions contains the set of actions that can be performed on + // this collection + CollectionActions = action.ActionSet{ + action.Create, + action.List, + } + + collectionTypeMap = map[resource.Type]action.ActionSet{ + resource.CredentialLibrary: credentiallibraries.CollectionActions, + } +) + +func init() { + var err error + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.CredentialStore{}, &store.Token{}, &store.ClientCertificate{}}, + handlers.MaskSource{&pb.CredentialStore{}, &pb.VaultCredentialStoreAttributes{}}); err != nil { + panic(err) + } +} + +// Service handles request as described by the pbs.CredentialStoreServiceServer interface. +type Service struct { + pbs.UnimplementedCredentialStoreServiceServer + + iamRepoFn common.IamRepoFactory + repoFn common.VaultCredentialRepoFactory +} + +// NewService returns a credential store service which handles credential store related requests to boundary. +func NewService(repo common.VaultCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) { + const op = "credentialstores.NewService" + if iamRepo == nil { + return Service{}, errors.New(errors.InvalidParameter, op, "missing iam repository") + } + if repo == nil { + return Service{}, errors.New(errors.InvalidParameter, op, "missing vault credential repository") + } + return Service{iamRepoFn: iamRepo, repoFn: repo}, nil +} + +var _ pbs.CredentialStoreServiceServer = Service{} + +// ListCredentialStores implements the interface pbs.CredentialStoreServiceServer +func (s Service) ListCredentialStores(ctx context.Context, req *pbs.ListCredentialStoresRequest) (*pbs.ListCredentialStoresResponse, error) { + if err := validateListRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetScopeId(), action.List) + if authResults.Error != nil { + // If it's forbidden, and it's a recursive request, and they're + // successfully authenticated but just not authorized, keep going as we + // may have authorization on downstream scopes. + if authResults.Error == handlers.ForbiddenError() && + req.GetRecursive() && + authResults.AuthenticationFinished { + } else { + return nil, authResults.Error + } + } + + scopeIds, scopeInfoMap, err := scopeids.GetListingScopeIds( + ctx, s.iamRepoFn, authResults, req.GetScopeId(), resource.CredentialStore, req.GetRecursive(), false) + if err != nil { + return nil, err + } + // If no scopes match, return an empty response + if len(scopeIds) == 0 { + return &pbs.ListCredentialStoresResponse{}, nil + } + + csl, err := s.listFromRepo(ctx, scopeIds) + if err != nil { + return nil, err + } + if len(csl) == 0 { + return &pbs.ListCredentialStoresResponse{}, nil + } + + filter, err := handlers.NewFilter(req.GetFilter()) + if err != nil { + return nil, err + } + finalItems := make([]*pb.CredentialStore, 0, len(csl)) + res := perms.Resource{ + Type: resource.CredentialStore, + } + for _, item := range csl { + res.Id = item.GetPublicId() + res.ScopeId = item.GetScopeId() + authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions, auth.WithResource(&res)).Strings() + if len(authorizedActions) == 0 { + continue + } + + outputFields := authResults.FetchOutputFields(res, action.List).SelfOrDefaults(authResults.UserId) + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(scopeInfoMap[item.GetScopeId()])) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) + } + if outputFields.Has(globals.AuthorizedCollectionActionsField) { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + + item, err := toProto(item, outputOpts...) + if err != nil { + return nil, err + } + + if filter.Match(item) { + finalItems = append(finalItems, item) + } + } + return &pbs.ListCredentialStoresResponse{Items: finalItems}, nil +} + +// GetCredentialStore implements the interface pbs.CredentialStoreServiceServer. +func (s Service) GetCredentialStore(ctx context.Context, req *pbs.GetCredentialStoreRequest) (*pbs.GetCredentialStoreResponse, error) { + const op = "credentialstores.(Service).GetCredentialStore" + + if err := validateGetRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Read) + if authResults.Error != nil { + return nil, authResults.Error + } + cs, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings())) + } + if outputFields.Has(globals.AuthorizedCollectionActionsField) { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + + item, err := toProto(cs, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.GetCredentialStoreResponse{Item: item}, nil +} + +// CreateCredentialStore implements the interface pbs.CredentialStoreServiceServer. +func (s Service) CreateCredentialStore(ctx context.Context, req *pbs.CreateCredentialStoreRequest) (*pbs.CreateCredentialStoreResponse, error) { + const op = "credentialstores.(Service).CreateCredentialStore" + + if err := validateCreateRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetItem().GetScopeId(), action.Create) + if authResults.Error != nil { + return nil, authResults.Error + } + cs, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings())) + } + if outputFields.Has(globals.AuthorizedCollectionActionsField) { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + + item, err := toProto(cs, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.CreateCredentialStoreResponse{ + Item: item, + Uri: fmt.Sprintf("credential-stores/%s", item.GetId()), + }, nil +} + +// UpdateCredentialStore implements the interface pbs.CredentialStoreServiceServer. +func (s Service) UpdateCredentialStore(ctx context.Context, req *pbs.UpdateCredentialStoreRequest) (*pbs.UpdateCredentialStoreResponse, error) { + const op = "credentialstores.(Service).UpdateCredentialStore" + + if err := validateUpdateRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Update) + if authResults.Error != nil { + return nil, authResults.Error + } + cs, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, cs.GetPublicId(), IdActions).Strings())) + } + if outputFields.Has(globals.AuthorizedCollectionActionsField) { + collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, cs.GetPublicId()) + if err != nil { + return nil, err + } + outputOpts = append(outputOpts, handlers.WithAuthorizedCollectionActions(collectionActions)) + } + + item, err := toProto(cs, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.UpdateCredentialStoreResponse{Item: item}, nil +} + +// DeleteCredentialStore implements the interface pbs.CredentialStoreServiceServer. +func (s Service) DeleteCredentialStore(ctx context.Context, req *pbs.DeleteCredentialStoreRequest) (*pbs.DeleteCredentialStoreResponse, error) { + if err := validateDeleteRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.Delete) + if authResults.Error != nil { + return nil, authResults.Error + } + _, err := s.deleteFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + return nil, nil +} + +func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*vault.CredentialStore, error) { + const op = "credentialstores.(Service).listFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + csl, err := repo.ListCredentialStores(ctx, scopeIds) + if err != nil { + return nil, errors.Wrap(err, op) + } + return csl, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (credential.Store, error) { + const op = "credentialstores.(Service).getFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + cs, err := repo.LookupCredentialStore(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return nil, errors.Wrap(err, op) + } + if cs == nil { + return nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("credential store %q not found", id)) + } + return cs, err +} + +func (s Service) createInRepo(ctx context.Context, projId string, item *pb.CredentialStore) (credential.Store, error) { + const op = "credentialstores.(Service).createInRepo" + cs, err := toStorageVaultStore(projId, item) + if err != nil { + return nil, errors.Wrap(err, op) + } + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + out, err := repo.CreateCredentialStore(ctx, cs) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to create credential store")) + } + if out == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential store but no error returned from repository.") + } + return out, nil +} + +func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []string, item *pb.CredentialStore) (credential.Store, error) { + const op = "credentialstores.(Service).updateInRepo" + cs, err := toStorageVaultStore(projId, item) + if err != nil { + return nil, errors.Wrap(err, op) + } + cs.PublicId = id + + dbMask := maskManager.Translate(mask) + if len(dbMask) == 0 { + return nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid fields provided in the update mask."}) + } + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + out, rowsUpdated, err := repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to update credential store")) + } + if rowsUpdated == 0 { + return nil, handlers.NotFoundErrorf("Credential Store %q doesn't exist or incorrect version provided.", id) + } + return out, nil +} + +func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { + const op = "credentialstores.(Service).deleteFromRepo" + repo, err := s.repoFn() + if err != nil { + return false, err + } + rows, err := repo.DeleteCredentialStore(ctx, id) + if err != nil { + if errors.IsNotFoundError(err) { + return false, nil + } + return false, errors.Wrap(err, op, errors.WithMsg("unable to delete credential store")) + } + return rows > 0, nil +} + +func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults { + res := auth.VerifyResults{} + iamRepo, err := s.iamRepoFn() + if err != nil { + res.Error = err + return res + } + repo, err := s.repoFn() + if err != nil { + res.Error = err + return res + } + + var parentId string + opts := []auth.Option{auth.WithType(resource.CredentialStore), auth.WithAction(a)} + switch a { + case action.List, action.Create: + parentId = id + scp, err := iamRepo.LookupScope(ctx, parentId) + if err != nil { + res.Error = err + return res + } + if scp == nil { + res.Error = handlers.NotFoundError() + return res + } + default: + cs, err := repo.LookupCredentialStore(ctx, id) + if err != nil { + res.Error = err + return res + } + if cs == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cs.GetScopeId() + opts = append(opts, auth.WithId(id)) + } + opts = append(opts, auth.WithScopeId(parentId)) + return auth.Verify(ctx, opts...) +} + +func toProto(in credential.Store, opt ...handlers.Option) (*pb.CredentialStore, error) { + const op = "credentialstores.toProto" + + opts := handlers.GetOpts(opt...) + if opts.WithOutputFields == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building credential store proto") + } + outputFields := *opts.WithOutputFields + + out := pb.CredentialStore{} + if outputFields.Has(globals.IdField) { + out.Id = in.GetPublicId() + } + if outputFields.Has(globals.ScopeIdField) { + out.ScopeId = in.GetScopeId() + } + if outputFields.Has(globals.TypeField) { + out.Type = credential.SubtypeFromId(in.GetPublicId()).String() + } + if outputFields.Has(globals.DescriptionField) && in.GetDescription() != "" { + out.Description = wrapperspb.String(in.GetDescription()) + } + if outputFields.Has(globals.NameField) && in.GetName() != "" { + out.Name = wrapperspb.String(in.GetName()) + } + if outputFields.Has(globals.CreatedTimeField) { + out.CreatedTime = in.GetCreateTime().GetTimestamp() + } + if outputFields.Has(globals.UpdatedTimeField) { + out.UpdatedTime = in.GetUpdateTime().GetTimestamp() + } + if outputFields.Has(globals.VersionField) { + out.Version = in.GetVersion() + } + if outputFields.Has(globals.ScopeField) { + out.Scope = opts.WithScope + } + if outputFields.Has(globals.AuthorizedActionsField) { + out.AuthorizedActions = opts.WithAuthorizedActions + } + if outputFields.Has(globals.AttributesField) { + switch credential.SubtypeFromId(in.GetPublicId()) { + case credential.VaultSubtype: + vaultIn, ok := in.(*vault.CredentialStore) + if !ok { + return nil, errors.New(errors.Internal, op, "unable to cast to vault credential store") + } + attrs := &pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(vaultIn.GetVaultAddress()), + } + if vaultIn.GetNamespace() != "" { + attrs.Namespace = wrapperspb.String(vaultIn.GetNamespace()) + } + if len(vaultIn.GetCaCert()) != 0 { + attrs.CaCert = wrapperspb.String(string(vaultIn.GetCaCert())) + } + if vaultIn.GetTlsServerName() != "" { + attrs.TlsServerName = wrapperspb.String(vaultIn.GetTlsServerName()) + } + if vaultIn.GetTlsSkipVerify() { + attrs.TlsSkipVerify = wrapperspb.Bool(vaultIn.GetTlsSkipVerify()) + } + if vaultIn.Token() != nil { + attrs.TokenHmac = base64.RawURLEncoding.EncodeToString(vaultIn.Token().GetTokenHmac()) + } + if cc := vaultIn.ClientCertificate(); cc != nil { + if len(cc.GetCertificate()) != 0 { + attrs.ClientCertificate = wrapperspb.String(string(cc.GetCertificate())) + } + attrs.ClientCertificateKeyHmac = base64.RawURLEncoding.EncodeToString(cc.GetCertificateKeyHmac()) + } + + var err error + if out.Attributes, err = handlers.ProtoToStruct(attrs); err != nil { + return nil, errors.Wrap(err, op) + } + } + } + return &out, nil +} + +func toStorageVaultStore(scopeId string, in *pb.CredentialStore) (out *vault.CredentialStore, err error) { + const op = "credentialstores.toStorageVaultStore" + var opts []vault.Option + if in.GetName() != nil { + opts = append(opts, vault.WithName(in.GetName().GetValue())) + } + if in.GetDescription() != nil { + opts = append(opts, vault.WithDescription(in.GetDescription().GetValue())) + } + + attrs := &pb.VaultCredentialStoreAttributes{} + if err := handlers.StructToProto(in.GetAttributes(), attrs); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to parse the attributes")) + } + if attrs.GetTlsServerName() != nil { + opts = append(opts, vault.WithTlsServerName(attrs.GetTlsServerName().GetValue())) + } + if attrs.GetTlsSkipVerify().GetValue() { + opts = append(opts, vault.WithTlsSkipVerify(attrs.GetTlsSkipVerify().GetValue())) + } + if attrs.GetNamespace().GetValue() != "" { + opts = append(opts, vault.WithNamespace(attrs.GetNamespace().GetValue())) + } + + // TODO (ICU-1478 and ICU-1479): Update the vault's interface around ca cert to match oidc's, + // accepting x509.Certificate instead of []byte + if attrs.GetCaCert() != nil { + opts = append(opts, vault.WithCACert([]byte(attrs.GetCaCert().GetValue()))) + } + pemCerts, pemPk, err := extractClientCertAndPk(attrs.GetClientCertificate().GetValue(), attrs.GetClientCertificateKey().GetValue()) + if err != nil { + return nil, errors.Wrap(err, op) + } + if len(pemCerts) != 0 { + var cert []byte + for _, c := range pemCerts { + cert = append(cert, pem.EncodeToMemory(c)...) + } + var pk []byte + if pemPk != nil { + pk = pem.EncodeToMemory(pemPk) + } + cc, err := vault.NewClientCertificate(cert, pk) + if err != nil { + return nil, errors.Wrap(err, op) + } + opts = append(opts, vault.WithClientCert(cc)) + } + + cs, err := vault.NewCredentialStore(scopeId, attrs.GetAddress().GetValue(), []byte(attrs.GetToken().GetValue()), opts...) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("unable to build credential store for creation")) + } + return cs, err +} + +// A validateX method should exist for each method above. These methods do not make calls to any backing service but enforce +// requirements on the structure of the request. They verify that: +// * The path passed in is correctly formatted +// * All required parameters are set +// * There are no conflicting parameters provided +func validateGetRequest(req *pbs.GetCredentialStoreRequest) error { + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix) +} + +func validateCreateRequest(req *pbs.CreateCredentialStoreRequest) error { + return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetItem().GetScopeId()), scope.Project.Prefix()) { + badFields["scope_id"] = "This field must be a valid project scope id." + } + switch credential.SubtypeFromType(req.GetItem().GetType()) { + case credential.VaultSubtype: + attrs := &pb.VaultCredentialStoreAttributes{} + if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { + badFields[globals.AttributesField] = fmt.Sprintf("Attribute fields do not match the expected format. Got %#v", req.GetItem().GetAttributes().AsMap()) + break + } + if attrs.GetAddress().GetValue() == "" { + badFields[addressField] = "Field required for creating a vault credential store." + } + if attrs.GetToken().GetValue() == "" { + badFields[vaultTokenField] = "Field required for creating a vault credential store." + } + if attrs.GetTokenHmac() != "" { + badFields[vaultTokenHmacField] = "This is a read only field." + } + + // TODO(ICU-1478 and ICU-1479): Validate client and CA certificate payloads + _, err := decodePemBlocks(attrs.GetCaCert().GetValue()) + if attrs.GetCaCert() != nil && err != nil { + badFields[caCertsField] = "Incorrectly formatted value." + } + + cs, pk, err := extractClientCertAndPk(attrs.GetClientCertificate().GetValue(), attrs.GetClientCertificateKey().GetValue()) + if err != nil { + badFields[clientCertField] = fmt.Sprintf("Invalid values: %q", err.Error()) + } + if attrs.GetClientCertificate() == nil && attrs.GetClientCertificateKey() != nil { + badFields[clientCertKeyField] = "Cannot set a client certificate private key without the client certificate." + } + if len(cs) > 0 && pk == nil { + badFields[clientCertField] = "Cannot set a client certificate without a private key." + } + default: + badFields[globals.TypeField] = "This is a required field and must be a known credential store type." + } + return badFields + }) +} + +func validateUpdateRequest(req *pbs.UpdateCredentialStoreRequest) error { + return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { + badFields := map[string]string{} + switch credential.SubtypeFromId(req.GetId()) { + case credential.VaultSubtype: + if req.GetItem().GetType() != "" && credential.SubtypeFromType(req.GetItem().GetType()) != credential.VaultSubtype { + badFields["type"] = "Cannot modify resource type." + } + attrs := &pb.VaultCredentialStoreAttributes{} + if err := handlers.StructToProto(req.GetItem().GetAttributes(), attrs); err != nil { + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." + break + } + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), addressField) && + attrs.GetAddress().GetValue() == "" { + badFields[addressField] = "This is a required field and cannot be unset." + } + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), vaultTokenField) && + attrs.GetToken().GetValue() == "" { + badFields[vaultTokenField] = "This is a required field and cannot be unset." + } + if attrs.GetTokenHmac() != "" { + badFields[vaultTokenHmacField] = "This is a read only field." + } + + // TODO(ICU-1478 and ICU-1479): Validate client and CA certificate payloads + _, err := decodePemBlocks(attrs.GetCaCert().GetValue()) + if attrs.GetCaCert() != nil && err != nil { + badFields[caCertsField] = "Incorrectly formatted value." + } + + _, _, err = extractClientCertAndPk(attrs.GetClientCertificate().GetValue(), attrs.GetClientCertificateKey().GetValue()) + if err != nil { + badFields[clientCertField] = fmt.Sprintf("Invalid values: %q", err.Error()) + } + } + return badFields + }, vault.CredentialStorePrefix) +} + +func validateDeleteRequest(req *pbs.DeleteCredentialStoreRequest) error { + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix) +} + +func validateListRequest(req *pbs.ListCredentialStoresRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) && + !req.GetRecursive() { + badFields[globals.ScopeIdField] = "This field must be a valid project scope ID or the list operation must be recursive." + } + if _, err := handlers.NewFilter(req.GetFilter()); err != nil { + badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Improperly formatted identifier.", badFields) + } + return nil +} diff --git a/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go new file mode 100644 index 000000000..ae4634767 --- /dev/null +++ b/internal/servers/controller/handlers/credentialstores/credentialstore_service_test.go @@ -0,0 +1,1040 @@ +package credentialstores + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/base64" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentialstores" + scopepb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/hashicorp/boundary/internal/types/scope" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +var testAuthorizedActions = []string{"no-op", "read", "update", "delete"} + +func TestList(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prjNoStores := iam.TestScopes(t, iamRepo) + _, prj := iam.TestScopes(t, iamRepo) + + var wantStores []*pb.CredentialStore + for _, s := range vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 10) { + wantStores = append(wantStores, &pb.CredentialStore{ + Id: s.GetPublicId(), + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: s.GetCreateTime().GetTimestamp(), + UpdatedTime: s.GetUpdateTime().GetTimestamp(), + Version: s.GetVersion(), + Type: credential.VaultSubtype.String(), + AuthorizedActions: testAuthorizedActions, + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(s.GetVaultAddress()), + TokenHmac: base64.RawURLEncoding.EncodeToString(s.Token().GetTokenHmac()), + ClientCertificate: wrapperspb.String(string(s.ClientCertificate().GetCertificate())), + ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(s.ClientCertificate().GetCertificateKeyHmac()), + // TODO: Add all fields including tls related fields, namespace, etc... + }) + require.NoError(t, err) + return attrs + }(), + }) + } + + cases := []struct { + name string + req *pbs.ListCredentialStoresRequest + res *pbs.ListCredentialStoresResponse + err error + }{ + { + name: "List Many Stores", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId()}, + res: &pbs.ListCredentialStoresResponse{Items: wantStores}, + }, + { + name: "List No Stores", + req: &pbs.ListCredentialStoresRequest{ScopeId: prjNoStores.GetPublicId()}, + res: &pbs.ListCredentialStoresResponse{}, + }, + { + name: "Filter to One Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[1].GetId())}, + res: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]}, + }, + { + name: "Filter to No Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, + res: &pbs.ListCredentialStoresResponse{}, + }, + { + name: "Filter Bad Format", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: `"//id/"=="bad"`}, + err: handlers.InvalidArgumentErrorf("bad format", nil), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err, "Couldn't create new host set service.") + + // Test non-anonymous listing + got, gErr := s.ListCredentialStores(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId()), tc.req) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err), "ListCredentialStore(%q) got error %v, wanted %v", tc.req, gErr, tc.err) + return + } + require.NoError(t, gErr) + assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform())) + + // Test anonymous listing + got, gErr = s.ListCredentialStores(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) + require.NoError(t, gErr) + assert.Len(t, got.Items, len(tc.res.Items)) + for _, item := range got.GetItems() { + require.Nil(t, item.CreatedTime) + require.Nil(t, item.UpdatedTime) + require.Zero(t, item.Version) + } + }) + } +} + +func TestCreate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + defaultCreated := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0].GetCreateTime().GetTimestamp() + + cleanup := func(s Service) { + ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) + r, err := s.ListCredentialStores(ctx, &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId()}) + require.NoError(t, err) + for _, i := range r.GetItems() { + _, err := s.DeleteCredentialStore(ctx, &pbs.DeleteCredentialStoreRequest{Id: i.GetId()}) + require.NoError(t, err) + } + } + + v := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS)) + newToken := func() string { + _, token := v.CreateToken(t) + return token + } + + cases := []struct { + name string + req *pbs.CreateCredentialStoreRequest + res *pbs.CreateCredentialStoreResponse + idPrefix string + err error + wantErr bool + }{ + { + name: "missing ca certificate", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + ClientCertificateKey: wrapperspb.String(string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + wantErr: true, + }, + { + name: "Bad token", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String("madeup"), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + ClientCertificateKey: wrapperspb.String(string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + wantErr: true, + }, + { + name: "Define only client cert", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Define only client cert key", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificateKey: wrapperspb.String(string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Define key in both client cert payload and key field", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificateKey: wrapperspb.String(string(append(v.ClientCert, v.ClientKey...))), + ClientCertificate: wrapperspb.String(string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Id", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Id: vault.CredentialStorePrefix + "_notallowed", + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + CreatedTime: timestamppb.Now(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + UpdatedTime: timestamppb.Now(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Update Time", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + UpdatedTime: timestamppb.Now(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must specify type", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must specify vault VaultAddress", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must specify vault token", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + }) + require.NoError(t, err) + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Attributes must be valid for vault type", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + }) + require.NoError(t, err) + attrs.Fields["invalid"] = structpb.NewStringValue("foo") + return attrs + }(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create a valid vault CredentialStore with client cert and key in same field", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificate: wrapperspb.String(string(v.ClientCert) + string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + res: &pbs.CreateCredentialStoreResponse{ + Uri: fmt.Sprintf("credential-stores/%s_", vault.CredentialStorePrefix), + Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + CaCert: wrapperspb.String(string(v.CaCert)), + Address: wrapperspb.String(v.Addr), + TokenHmac: "", + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + ClientCertificateKeyHmac: "", + }) + require.NoError(t, err) + return attrs + }(), + AuthorizedActions: testAuthorizedActions, + }, + }, + }, + { + name: "Create a valid vault CredentialStore", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v.Addr), + Token: wrapperspb.String(newToken()), + CaCert: wrapperspb.String(string(v.CaCert)), + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + ClientCertificateKey: wrapperspb.String(string(v.ClientKey)), + }) + require.NoError(t, err) + return attrs + }(), + }}, + idPrefix: vault.CredentialStorePrefix + "_", + res: &pbs.CreateCredentialStoreResponse{ + Uri: fmt.Sprintf("credential-stores/%s_", vault.CredentialStorePrefix), + Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credential.VaultSubtype.String(), + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + CaCert: wrapperspb.String(string(v.CaCert)), + Address: wrapperspb.String(v.Addr), + TokenHmac: "", + ClientCertificate: wrapperspb.String(string(v.ClientCert)), + ClientCertificateKeyHmac: "", + }) + require.NoError(t, err) + return attrs + }(), + AuthorizedActions: testAuthorizedActions, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + s, err := NewService(repoFn, iamRepoFn) + require.NoError(err, "Error when getting new credential store service.") + defer cleanup(s) + + got, gErr := s.CreateCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetItem().GetScopeId()), tc.req) + if tc.wantErr || tc.err != nil { + require.Error(gErr) + if tc.err != nil { + assert.True(errors.Is(gErr, tc.err), "CreateCredentialStore(...) got error %v, wanted %v", gErr, tc.err) + } + return + } + require.NoError(gErr) + if tc.res == nil { + require.Nil(got) + } + if got != nil { + assert.Contains(got.GetUri(), tc.res.Uri) + assert.True(strings.HasPrefix(got.GetItem().GetId(), tc.idPrefix)) + gotCreateTime := got.GetItem().GetCreatedTime() + gotUpdateTime := got.GetItem().GetUpdatedTime() + + // Verify it is a credential store created after the test setup's default credential store + assert.True(gotCreateTime.AsTime().After(defaultCreated.AsTime()), "New credential store should have been created after default credential store. Was created %v, which is after %v", gotCreateTime, defaultCreated) + assert.True(gotUpdateTime.AsTime().After(defaultCreated.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated) + + // Clear all values which are hard to compare against. + got.Uri, tc.res.Uri = "", "" + got.Item.Id, tc.res.Item.Id = "", "" + got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + if _, ok := got.Item.Attributes.Fields["token_hmac"]; ok { + assert.NotEqual(tc.req.Item.Attributes.Fields["token"], got.Item.Attributes.Fields["token_hmac"]) + got.Item.Attributes.Fields["token_hmac"] = structpb.NewStringValue("") + } + if _, ok := got.Item.Attributes.Fields["client_certificate_key_hmac"]; ok { + assert.NotEqual(tc.req.Item.Attributes.Fields["client_certificate_key_hmac"], got.Item.Attributes.Fields["client_certificate_key_hmac"]) + got.Item.Attributes.Fields["client_certificate_key_hmac"] = structpb.NewStringValue("") + } + } + assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform(), protocmp.SortRepeatedFields(got)), "CreateCredentialStore(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestGet(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + + cases := []struct { + name string + id string + res *pbs.GetCredentialStoreResponse + err error + }{ + { + name: "success", + id: store.GetPublicId(), + res: &pbs.GetCredentialStoreResponse{ + Item: &pb.CredentialStore{ + Id: store.GetPublicId(), + ScopeId: store.GetScopeId(), + Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: credential.VaultSubtype.String(), + AuthorizedActions: testAuthorizedActions, + CreatedTime: store.CreateTime.GetTimestamp(), + UpdatedTime: store.UpdateTime.GetTimestamp(), + Version: 1, + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(store.GetVaultAddress()), + TokenHmac: base64.RawURLEncoding.EncodeToString(store.Token().GetTokenHmac()), + ClientCertificate: wrapperspb.String(string(store.ClientCertificate().GetCertificate())), + ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(store.ClientCertificate().GetCertificateKeyHmac()), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.HostPrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.GetCredentialStoreRequest{Id: tc.id} + // Test non-anonymous get + got, gErr := s.GetCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), req) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err)) + return + } + require.NoError(t, gErr) + assert.Empty(t, cmp.Diff(got, tc.res, protocmp.Transform())) + + // Test anonymous get + got, gErr = s.GetCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), req) + require.NoError(t, gErr) + require.Nil(t, got.Item.CreatedTime) + require.Nil(t, got.Item.UpdatedTime) + require.Zero(t, got.Item.Version) + }) + } +} + +func TestDelete(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0] + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + + cases := []struct { + name string + id string + err error + }{ + { + name: "success", + id: store.GetPublicId(), + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.HostPrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, gErr := s.DeleteCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialStoreRequest{Id: tc.id}) + assert.Nil(t, got) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err)) + return + } + require.NoError(t, gErr) + g, err := s.GetCredentialStore(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.GetCredentialStoreRequest{Id: tc.id}) + assert.Nil(t, g) + assert.True(t, errors.Is(err, handlers.NotFoundError())) + }) + } +} + +func TestUpdate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + sche := scheduler.TestScheduler(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + + _, prj := iam.TestScopes(t, iamRepo) + ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) + + s, err := NewService(repoFn, iamRepoFn) + require.NoError(t, err) + + fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { + return &fieldmaskpb.FieldMask{Paths: paths} + } + + key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) + require.NoError(t, err) + + v := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS), vault.WithClientKey(key)) + _, token1b := v.CreateToken(t) + clientCert, err := vault.NewClientCertificate(v.ClientCert, v.ClientKey) + require.NoError(t, err) + + v2 := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS), vault.WithClientKey(key)) + _, token2 := v2.CreateToken(t) + clientCert2, err := vault.NewClientCertificate(v2.ClientCert, v2.ClientKey) + require.NoError(t, err) + + freshStore := func() (*vault.CredentialStore, func()) { + t.Helper() + secret, token1a := v.CreateToken(t) + accessor := secret.Auth.Accessor + + st := vault.TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), v.Addr, token1a, accessor, vault.WithCACert(v.CaCert), vault.WithClientCert(clientCert)) + clean := func() { + _, err := s.DeleteCredentialStore(ctx, &pbs.DeleteCredentialStoreRequest{Id: st.GetPublicId()}) + require.NoError(t, err) + } + return st, clean + } + + successCases := []struct { + name string + req *pbs.UpdateCredentialStoreRequest + res func(*pb.CredentialStore) *pb.CredentialStore + }{ + { + name: "name and description", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("name", "description"), + Item: &pb.CredentialStore{ + Name: wrapperspb.String("basic"), + Description: wrapperspb.String("basic"), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Name = wrapperspb.String("basic") + out.Description = wrapperspb.String("basic") + return out + }, + }, + { + name: "update connection info", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.address", "attributes.client_certificate", "attributes.client_certificate_key", "attributes.ca_cert", "attributes.token"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Address: wrapperspb.String(v2.Addr), + Token: wrapperspb.String(token2), + ClientCertificate: wrapperspb.String(string(clientCert2.Certificate)), + ClientCertificateKey: wrapperspb.String(string(clientCert2.CertificateKey)), + CaCert: wrapperspb.String(string(v2.CaCert)), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["address"] = structpb.NewStringValue(v2.Addr) + out.Attributes.Fields["client_certificate"] = structpb.NewStringValue(string(clientCert2.Certificate)) + out.Attributes.Fields["ca_cert"] = structpb.NewStringValue(string(v2.CaCert)) + out.Attributes.Fields["token_hmac"] = structpb.NewStringValue("") + out.Attributes.Fields["client_certificate_key_hmac"] = structpb.NewStringValue("") + return out + }, + }, + { + name: "update token", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.token"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Token: wrapperspb.String(token1b), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["token_hmac"] = structpb.NewStringValue("") + return out + }, + }, + { + name: "unset certificate", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.client_certificate", "attributes.client_certificate_key"), + Item: &pb.CredentialStore{}, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + delete(out.Attributes.Fields, "client_certificate") + delete(out.Attributes.Fields, "client_certificate_key_hmac") + return out + }, + }, + { + name: "update namespace", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.namespace"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Namespace: wrapperspb.String("update namespace"), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["namespace"] = structpb.NewStringValue("update namespace") + return out + }, + }, + { + name: "update tls server name", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.tls_server_name"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + TlsServerName: wrapperspb.String("UpdateTlsServerName"), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["tls_server_name"] = structpb.NewStringValue("UpdateTlsServerName") + return out + }, + }, + { + name: "update TlsSkipVerify", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.tls_skip_verify"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + TlsSkipVerify: wrapperspb.Bool(true), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["tls_skip_verify"] = structpb.NewBoolValue(true) + return out + }, + }, + { + name: "update ca cert", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.ca_cert"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + CaCert: wrapperspb.String(string(v2.CaCert)), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["ca_cert"] = structpb.NewStringValue(string(v2.CaCert)) + return out + }, + }, + { + name: "update client cert", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("attributes.client_certificate"), + Item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + ClientCertificate: wrapperspb.String(string(v2.ClientCert)), + }) + require.NoError(t, err) + return attrs + }(), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Attributes.Fields["client_certificate"] = structpb.NewStringValue(string(v2.ClientCert)) + return out + }, + }, + } + + for _, tc := range successCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + st, cleanup := freshStore() + defer cleanup() + + if tc.req.Item.GetVersion() == 0 { + tc.req.Item.Version = 1 + } + if tc.req.GetId() == "" { + tc.req.Id = st.GetPublicId() + } + resToChange, err := s.GetCredentialStore(ctx, &pbs.GetCredentialStoreRequest{Id: st.GetPublicId()}) + require.NoError(err) + want := &pbs.UpdateCredentialStoreResponse{Item: tc.res(resToChange.GetItem())} + + got, gErr := s.UpdateCredentialStore(ctx, tc.req) + require.NoError(gErr) + require.NotNil(got) + + gotUpdateTime := got.GetItem().GetUpdatedTime() + created := st.GetCreateTime().GetTimestamp() + assert.True(gotUpdateTime.AsTime().After(created.AsTime()), "Should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, created) + + want.Item.UpdatedTime = got.Item.UpdatedTime + + assert.EqualValues(2, got.Item.Version) + want.Item.Version = 2 + + if v, ok := want.Item.GetAttributes().AsMap()["token_hmac"]; ok && v.(string) == "" { + got.Item.Attributes.Fields["token_hmac"] = structpb.NewStringValue("") + } + if v, ok := want.Item.GetAttributes().AsMap()["client_certificate_key_hmac"]; ok && v.(string) == "" { + got.Item.Attributes.Fields["client_certificate_key_hmac"] = structpb.NewStringValue("") + } + + assert.Empty(cmp.Diff(got, want, protocmp.Transform())) + }) + } + + // cant update read only fields + st, cleanup := freshStore() + defer cleanup() + + roCases := []struct { + path string + item *pb.CredentialStore + matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error + }{ + { + path: "type", + item: &pb.CredentialStore{Type: "something"}, + }, + { + path: "scope_id", + item: &pb.CredentialStore{ScopeId: "global"}, + }, + { + path: "updated_time", + item: &pb.CredentialStore{UpdatedTime: timestamppb.Now()}, + }, + { + path: "created_time", + item: &pb.CredentialStore{UpdatedTime: timestamppb.Now()}, + }, + { + path: "authorized_actions", + item: &pb.CredentialStore{AuthorizedActions: append(testAuthorizedActions, "another")}, + }, + { + // This fails because we do not update the vault address at the same time as + // updating the token. + path: "attributes.token", + item: &pb.CredentialStore{ + Attributes: func() *structpb.Struct { + attrs, err := handlers.ProtoToStruct(&pb.VaultCredentialStoreAttributes{ + Token: wrapperspb.String(token2), + }) + require.NoError(t, err) + return attrs + }(), + }, + matcher: func(t *testing.T, err error) { + assert.Containsf(t, err.Error(), "cannot lookup token for updated store", "got error %v, wanted 'unable to lookup token'", err) + }, + }, + } + for _, tc := range roCases { + t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) { + req := &pbs.UpdateCredentialStoreRequest{ + Id: st.GetPublicId(), + Item: tc.item, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}}, + } + req.Item.Version = st.Version + + got, gErr := s.UpdateCredentialStore(ctx, req) + assert.Error(t, gErr) + matcher := tc.matcher + if matcher == nil { + matcher = func(t *testing.T, e error) { + assert.Truef(t, errors.Is(gErr, handlers.ApiErrorWithCode(codes.InvalidArgument)), "got error %v, wanted invalid argument", gErr) + } + } + matcher(t, gErr) + assert.Nil(t, got) + }) + } +} diff --git a/internal/servers/controller/handlers/credentialstores/vault.go b/internal/servers/controller/handlers/credentialstores/vault.go new file mode 100644 index 000000000..790a482b0 --- /dev/null +++ b/internal/servers/controller/handlers/credentialstores/vault.go @@ -0,0 +1,70 @@ +package credentialstores + +import ( + "encoding/pem" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/errors" +) + +// extractClientCertAndPk takes the values passed into the api for client +// certificate and client certificate key and parses them into pem blocks. +// Any non certificate pem blocks are treated as a private key. An error is +// returned if there is more than 1 private key provided across both fields. +func extractClientCertAndPk(cert, pk string) ([]*pem.Block, *pem.Block, error) { + const op = "credentialstores.extractClientCertAndPk" + pks, err := decodePemBlocks(pk) + if err != nil { + return nil, nil, errors.Wrap(err, op, errors.WithMsg("failed to parse client certificate private key")) + } + var pkPem *pem.Block + switch len(pks) { + case 0: + case 1: + pkPem = pks[0] + default: + return nil, nil, errors.New(errors.InvalidParameter, op, "private key payload contained multiple pem blocks") + } + + bs, err := decodePemBlocks(cert) + if err != nil { + return nil, nil, errors.Wrap(err, op, errors.WithMsg("failed to parse client certificate into pem blocks")) + } + pkIdx := -1 + for i, b := range bs { + if !strings.Contains(b.Type, "CERTIFICATE") { + switch { + case pkPem == nil: + pkIdx, pkPem = i, b + case pkIdx < 0: + return nil, nil, errors.New(errors.InvalidParameter, op, "client certificate contains a private key when one was also provided separately") + default: + return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("second primary key found at %d after previous one found at %d", i, pkIdx)) + } + } + } + if pkIdx >= 0 { + bs = append(bs[:pkIdx], bs[pkIdx+1:]...) + } + return bs, pkPem, nil +} + +func decodePemBlocks(input string) ([]*pem.Block, error) { + const op = "credentialstores.decodePemBlocks" + cpIn := make([]byte, len(input)) + copy(cpIn, input) + var p *pem.Block + var ret []*pem.Block + for { + p, cpIn = pem.Decode(cpIn) + if p == nil { + break + } + ret = append(ret, p) + } + if len(cpIn) > 0 { + return nil, errors.New(errors.InvalidParameter, op, "not all data parseable by pem block") + } + return ret, nil +} diff --git a/internal/servers/controller/handlers/credentialstores/vault_test.go b/internal/servers/controller/handlers/credentialstores/vault_test.go new file mode 100644 index 000000000..b330a6ddf --- /dev/null +++ b/internal/servers/controller/handlers/credentialstores/vault_test.go @@ -0,0 +1,61 @@ +package credentialstores + +import ( + "encoding/pem" + "testing" + + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPkAndClientCerts(t *testing.T) { + s := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestClientTLS)) + + t.Run("seperate client cert and key", func(t *testing.T) { + c, k, err := extractClientCertAndPk(string(s.ClientCert), string(s.ClientKey)) + assert.NoError(t, err) + require.NotNil(t, k) + assert.Equal(t, s.ClientKey, pem.EncodeToMemory(k)) + require.Len(t, c, 1) + assert.Equal(t, s.ClientCert, pem.EncodeToMemory(c[0])) + }) + + t.Run("client cert and key bundled in cert", func(t *testing.T) { + c, k, err := extractClientCertAndPk(string(s.ClientCert)+string(s.ClientKey), "") + assert.NoError(t, err) + require.NotNil(t, k) + assert.Equal(t, s.ClientKey, pem.EncodeToMemory(k)) + require.Len(t, c, 1) + assert.Equal(t, s.ClientCert, pem.EncodeToMemory(c[0])) + }) + + t.Run("cert with no private key", func(t *testing.T) { + c, k, err := extractClientCertAndPk(string(s.ClientCert), "") + assert.NoError(t, err) + assert.Nil(t, k) + require.Len(t, c, 1) + assert.Equal(t, s.ClientCert, pem.EncodeToMemory(c[0])) + }) + + t.Run("private key with no cert", func(t *testing.T) { + c, k, err := extractClientCertAndPk("", string(s.ClientKey)) + assert.NoError(t, err) + assert.NotNil(t, k) + assert.Equal(t, s.ClientKey, pem.EncodeToMemory(k)) + require.Empty(t, c) + }) + + t.Run("error cases", func(t *testing.T) { + _, _, err := extractClientCertAndPk(string(s.ClientCert), "invalid key") + assert.Error(t, err) + _, _, err = extractClientCertAndPk("invalid cert", string(s.ClientKey)) + assert.Error(t, err) + + // private key with the cert and the private key + c, k, err := extractClientCertAndPk(string(s.ClientCert)+string(s.ClientKey), string(s.ClientKey)) + assert.Error(t, err) + assert.Nil(t, k) + assert.Empty(t, c) + }) +} diff --git a/internal/servers/controller/handlers/groups/group_service.go b/internal/servers/controller/handlers/groups/group_service.go index f538c8e7b..3cc23019c 100644 --- a/internal/servers/controller/handlers/groups/group_service.go +++ b/internal/servers/controller/handlers/groups/group_service.go @@ -49,7 +49,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.Group{}, &pb.Group{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.Group{}}, handlers.MaskSource{&pb.Group{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go index 3348f4d13..986b63bbd 100644 --- a/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go +++ b/internal/servers/controller/handlers/host_catalogs/host_catalog_service.go @@ -53,7 +53,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.HostCatalog{}, &pb.HostCatalog{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostCatalog{}}, handlers.MaskSource{&pb.HostCatalog{}}); err != nil { panic(err) } } @@ -342,7 +342,7 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*static } func (s Service) createInRepo(ctx context.Context, projId string, item *pb.HostCatalog) (*static.HostCatalog, error) { - const op = "host_catalogs.(Servivce).createInRepo" + const op = "host_catalogs.(Service).createInRepo" var opts []static.Option if item.GetName() != nil { opts = append(opts, static.WithName(item.GetName().GetValue())) diff --git a/internal/servers/controller/handlers/host_sets/host_set_service.go b/internal/servers/controller/handlers/host_sets/host_set_service.go index 6fff324e2..636f6debf 100644 --- a/internal/servers/controller/handlers/host_sets/host_set_service.go +++ b/internal/servers/controller/handlers/host_sets/host_set_service.go @@ -48,7 +48,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.HostSet{}, &pb.HostSet{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.HostSet{}}, handlers.MaskSource{&pb.HostSet{}}); err != nil { panic(err) } } @@ -172,11 +172,11 @@ func (s Service) CreateHostSet(ctx context.Context, req *pbs.CreateHostSetReques if err := validateCreateRequest(req); err != nil { return nil, err } - cat, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetHostCatalogId(), action.Create) + _, authResults := s.parentAndAuthResult(ctx, req.GetItem().GetHostCatalogId(), action.Create) if authResults.Error != nil { return nil, authResults.Error } - hs, err := s.createInRepo(ctx, authResults.Scope.GetId(), cat.GetPublicId(), req.GetItem()) + hs, err := s.createInRepo(ctx, authResults.Scope.GetId(), req.GetItem().GetHostCatalogId(), req.GetItem()) if err != nil { return nil, err } diff --git a/internal/servers/controller/handlers/hosts/host_service.go b/internal/servers/controller/handlers/hosts/host_service.go index ed7185635..a9d77ca3a 100644 --- a/internal/servers/controller/handlers/hosts/host_service.go +++ b/internal/servers/controller/handlers/hosts/host_service.go @@ -46,7 +46,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.Host{}, &pb.Host{}, &pb.StaticHostAttributes{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.Host{}}, handlers.MaskSource{&pb.Host{}, &pb.StaticHostAttributes{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/managed_groups/managed_group_service.go b/internal/servers/controller/handlers/managed_groups/managed_group_service.go index e85879703..75c613e63 100644 --- a/internal/servers/controller/handlers/managed_groups/managed_group_service.go +++ b/internal/servers/controller/handlers/managed_groups/managed_group_service.go @@ -52,7 +52,7 @@ var ( func init() { var err error - if oidcMaskManager, err = handlers.NewMaskManager(&oidcstore.ManagedGroup{}, &pb.ManagedGroup{}, &pb.OidcManagedGroupAttributes{}); err != nil { + if oidcMaskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&oidcstore.ManagedGroup{}}, handlers.MaskSource{&pb.ManagedGroup{}, &pb.OidcManagedGroupAttributes{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/mask_manager.go b/internal/servers/controller/handlers/mask_manager.go index 2e56f920d..ccc73ba75 100644 --- a/internal/servers/controller/handlers/mask_manager.go +++ b/internal/servers/controller/handlers/mask_manager.go @@ -14,12 +14,17 @@ import ( type MaskManager map[string]string +type ( + MaskDestination []protoreflect.ProtoMessage + MaskSource []protoreflect.ProtoMessage +) + // NewMaskManager returns a mask manager that can translate field masks into the first proto from all subsequent // protos assuming they are both using the mask_mapping custom option. Error is returned if no mappings are // found or if one of the passed protos has a mapping that doesn't reciprocate. -func NewMaskManager(dest protoreflect.ProtoMessage, src ...protoreflect.ProtoMessage) (MaskManager, error) { +func NewMaskManager(dest MaskDestination, src MaskSource) (MaskManager, error) { const op = "handlers.NewMaskManager" - srcToDest, err := mapFromProto(src...) + srcToDest, err := mapFromProto(src) if err != nil { return nil, err } @@ -51,7 +56,7 @@ func NewMaskManager(dest protoreflect.ProtoMessage, src ...protoreflect.ProtoMes return result, nil } -func mapFromProto(ps ...protoreflect.ProtoMessage) (map[string]string, error) { +func mapFromProto(ps []protoreflect.ProtoMessage) (map[string]string, error) { const op = "handlers.mapFromProto" mapping := make(map[string]string) for _, p := range ps { diff --git a/internal/servers/controller/handlers/mask_manager_test.go b/internal/servers/controller/handlers/mask_manager_test.go index 715b7eca0..00baa8a3a 100644 --- a/internal/servers/controller/handlers/mask_manager_test.go +++ b/internal/servers/controller/handlers/mask_manager_test.go @@ -9,7 +9,7 @@ import ( ) func TestMaskManager(t *testing.T) { - mm, err := NewMaskManager(&pb.TestProperlyNamedFields{}, &pb.TestBase{}) + mm, err := NewMaskManager(MaskDestination{&pb.TestProperlyNamedFields{}}, MaskSource{&pb.TestBase{}}) require.NoError(t, err) assert.Equal(t, []string(nil), mm.Translate([]string{"doesnt_exist"})) assert.Equal(t, []string{"OtherFirstField"}, mm.Translate([]string{"first_field"})) @@ -20,7 +20,7 @@ func TestMaskManager(t *testing.T) { } func TestMaskManager_Split(t *testing.T) { - mm, err := NewMaskManager(&pb.TestProperlyNamedFields{}, &pb.TestBaseSplit1{}, &pb.TestBaseSplit2{}) + mm, err := NewMaskManager(MaskDestination{&pb.TestProperlyNamedFields{}}, MaskSource{&pb.TestBaseSplit1{}, &pb.TestBaseSplit2{}}) require.NoError(t, err) assert.Equal(t, []string(nil), mm.Translate([]string{"doesnt_exist"})) assert.Equal(t, []string{"OtherFirstField"}, mm.Translate([]string{"first_field"})) @@ -31,10 +31,10 @@ func TestMaskManager_Split(t *testing.T) { } func TestMaskManager_errors(t *testing.T) { - _, err := NewMaskManager(&pb.TestBase{}, &pb.TestManyToOneMappings{}) + _, err := NewMaskManager(MaskDestination{&pb.TestBase{}}, MaskSource{&pb.TestManyToOneMappings{}}) assert.Error(t, err) - _, err = NewMaskManager(&pb.TestBase{}, &pb.TestNameDoesntMap{}) + _, err = NewMaskManager(MaskDestination{&pb.TestBase{}}, MaskSource{&pb.TestNameDoesntMap{}}) assert.Error(t, err) - _, err = NewMaskManager(&pb.TestBase{}, &pb.TestNotEnoughFields{}) + _, err = NewMaskManager(MaskDestination{&pb.TestBase{}}, MaskSource{&pb.TestNotEnoughFields{}}) assert.Error(t, err) } diff --git a/internal/servers/controller/handlers/roles/role_service.go b/internal/servers/controller/handlers/roles/role_service.go index 57dc33594..0b3e3604e 100644 --- a/internal/servers/controller/handlers/roles/role_service.go +++ b/internal/servers/controller/handlers/roles/role_service.go @@ -53,7 +53,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.Role{}, &pb.Role{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.Role{}}, handlers.MaskSource{&pb.Role{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/scopes/scope_service.go b/internal/servers/controller/handlers/scopes/scope_service.go index 3cc5f66ed..9ca1b0b14 100644 --- a/internal/servers/controller/handlers/scopes/scope_service.go +++ b/internal/servers/controller/handlers/scopes/scope_service.go @@ -86,7 +86,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.Scope{}, &pb.Scope{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.Scope{}}, handlers.MaskSource{&pb.Scope{}}); err != nil { panic(err) } } diff --git a/internal/servers/controller/handlers/targets/target_service.go b/internal/servers/controller/handlers/targets/target_service.go index f60a9f558..721ee0206 100644 --- a/internal/servers/controller/handlers/targets/target_service.go +++ b/internal/servers/controller/handlers/targets/target_service.go @@ -2,6 +2,8 @@ package targets import ( "context" + "encoding/base64" + "encoding/json" stderrors "errors" "fmt" "math/rand" @@ -11,6 +13,8 @@ import ( "github.com/golang/protobuf/ptypes/wrappers" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/errors" pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/targets" @@ -53,6 +57,9 @@ var ( action.AddHostSets, action.SetHostSets, action.RemoveHostSets, + action.AddCredentialLibraries, + action.SetCredentialLibraries, + action.RemoveCredentialLibraries, action.AuthorizeSession, } @@ -66,7 +73,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.TcpTarget{}, &pb.Target{}, &pb.TcpTargetAttributes{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.TcpTarget{}}, handlers.MaskSource{&pb.Target{}, &pb.TcpTargetAttributes{}}); err != nil { panic(err) } } @@ -80,6 +87,7 @@ type Service struct { serversRepoFn common.ServersRepoFactory sessionRepoFn common.SessionRepoFactory staticHostRepoFn common.StaticRepoFactory + vaultCredRepoFn common.VaultCredentialRepoFactory kmsCache *kms.Kms } @@ -90,7 +98,8 @@ func NewService( iamRepoFn common.IamRepoFactory, serversRepoFn common.ServersRepoFactory, sessionRepoFn common.SessionRepoFactory, - staticHostRepoFn common.StaticRepoFactory) (Service, error) { + staticHostRepoFn common.StaticRepoFactory, + vaultCredRepoFn common.VaultCredentialRepoFactory) (Service, error) { const op = "targets.NewService" if repoFn == nil { return Service{}, errors.New(errors.InvalidParameter, op, "missing target repository") @@ -107,12 +116,16 @@ func NewService( if staticHostRepoFn == nil { return Service{}, errors.New(errors.InvalidParameter, op, "missing static host repository") } + if vaultCredRepoFn == nil { + return Service{}, errors.New(errors.InvalidParameter, op, "missing vault credential repository") + } return Service{ repoFn: repoFn, iamRepoFn: iamRepoFn, serversRepoFn: serversRepoFn, sessionRepoFn: sessionRepoFn, staticHostRepoFn: staticHostRepoFn, + vaultCredRepoFn: vaultCredRepoFn, kmsCache: kmsCache, }, nil } @@ -181,7 +194,7 @@ func (s Service) ListTargets(ctx context.Context, req *pbs.ListTargetsRequest) ( outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } - item, err := toProto(ctx, item, nil, outputOpts...) + item, err := toProto(ctx, item, nil, nil, outputOpts...) if err != nil { return nil, err } @@ -204,7 +217,7 @@ func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.getFromRepo(ctx, req.GetId()) + t, ts, cl, err := s.getFromRepo(ctx, req.GetId()) if err != nil { return nil, err } @@ -223,7 +236,7 @@ func (s Service) GetTarget(ctx context.Context, req *pbs.GetTargetRequest) (*pbs outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -242,7 +255,7 @@ func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest) if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.createInRepo(ctx, req.GetItem()) + t, ts, cl, err := s.createInRepo(ctx, req.GetItem()) if err != nil { return nil, err } @@ -261,7 +274,7 @@ func (s Service) CreateTarget(ctx context.Context, req *pbs.CreateTargetRequest) outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -280,7 +293,7 @@ func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest) if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + t, ts, cl, err := s.updateInRepo(ctx, authResults.Scope.GetId(), req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) if err != nil { return nil, err } @@ -299,7 +312,7 @@ func (s Service) UpdateTarget(ctx context.Context, req *pbs.UpdateTargetRequest) outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -327,14 +340,14 @@ func (s Service) DeleteTarget(ctx context.Context, req *pbs.DeleteTargetRequest) func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSetsRequest) (*pbs.AddTargetHostSetsResponse, error) { const op = "targets.(Service).AddTargetHostSets" - if err := validateAddRequest(req); err != nil { + if err := validateAddSetsRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.AddHostSets) if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.addInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) + t, ts, cl, err := s.addSetsInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) if err != nil { return nil, err } @@ -353,7 +366,7 @@ func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSe outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -365,14 +378,14 @@ func (s Service) AddTargetHostSets(ctx context.Context, req *pbs.AddTargetHostSe func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSetsRequest) (*pbs.SetTargetHostSetsResponse, error) { const op = "targets.(Service).SetTargetHostSets" - if err := validateSetRequest(req); err != nil { + if err := validateSetSetsRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.SetHostSets) if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.setInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) + t, ts, cl, err := s.setSetsInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) if err != nil { return nil, err } @@ -391,7 +404,7 @@ func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSe outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -403,14 +416,14 @@ func (s Service) SetTargetHostSets(ctx context.Context, req *pbs.SetTargetHostSe func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTargetHostSetsRequest) (*pbs.RemoveTargetHostSetsResponse, error) { const op = "targets.(Service).RemoveTargetHostSets" - if err := validateRemoveRequest(req); err != nil { + if err := validateRemoveSetsRequest(req); err != nil { return nil, err } authResults := s.authResult(ctx, req.GetId(), action.RemoveHostSets) if authResults.Error != nil { return nil, authResults.Error } - t, ts, err := s.removeInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) + t, ts, cl, err := s.removeSetsInRepo(ctx, req.GetId(), req.GetHostSetIds(), req.GetVersion()) if err != nil { return nil, err } @@ -429,7 +442,7 @@ func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTarget outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) } - item, err := toProto(ctx, t, ts, outputOpts...) + item, err := toProto(ctx, t, ts, cl, outputOpts...) if err != nil { return nil, err } @@ -437,6 +450,120 @@ func (s Service) RemoveTargetHostSets(ctx context.Context, req *pbs.RemoveTarget return &pbs.RemoveTargetHostSetsResponse{Item: item}, nil } +// AddTargetCredentialLibraries implements the interface pbs.TargetServiceServer. +func (s Service) AddTargetCredentialLibraries(ctx context.Context, req *pbs.AddTargetCredentialLibrariesRequest) (*pbs.AddTargetCredentialLibrariesResponse, error) { + const op = "targets.(Service).AddTargetCredentialLibraries" + + if err := validateAddLibrariesRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.AddCredentialLibraries) + if authResults.Error != nil { + return nil, authResults.Error + } + t, ts, cl, err := s.addLibrariesInRepo(ctx, req.GetId(), req.GetCredentialLibraryIds(), req.GetCredentialLibraries(), req.GetVersion()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, t, ts, cl, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.AddTargetCredentialLibrariesResponse{Item: item}, nil +} + +// SetTargetCredentialLibraries implements the interface pbs.TargetServiceServer. +func (s Service) SetTargetCredentialLibraries(ctx context.Context, req *pbs.SetTargetCredentialLibrariesRequest) (*pbs.SetTargetCredentialLibrariesResponse, error) { + const op = "targets.(Service).SetTargetCredentialLibraries" + + if err := validateSetLibrariesRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.SetCredentialLibraries) + if authResults.Error != nil { + return nil, authResults.Error + } + t, ts, cl, err := s.setLibrariesInRepo(ctx, req.GetId(), req.GetCredentialLibraryIds(), req.GetCredentialLibraries(), req.GetVersion()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, t, ts, cl, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.SetTargetCredentialLibrariesResponse{Item: item}, nil +} + +// RemoveTargetCredentialLibraries implements the interface pbs.TargetServiceServer. +func (s Service) RemoveTargetCredentialLibraries(ctx context.Context, req *pbs.RemoveTargetCredentialLibrariesRequest) (*pbs.RemoveTargetCredentialLibrariesResponse, error) { + const op = "targets.(Service).RemoveTargetCredentialLibraries" + + if err := validateRemoveLibrariesRequest(req); err != nil { + return nil, err + } + authResults := s.authResult(ctx, req.GetId(), action.RemoveCredentialLibraries) + if authResults.Error != nil { + return nil, authResults.Error + } + t, ts, cl, err := s.removeLibrariesInRepo(ctx, req.GetId(), req.GetCredentialLibraryIds(), req.GetVersion()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(errors.Internal, op, "no request context found") + } + + outputOpts := make([]handlers.Option, 0, 3) + outputOpts = append(outputOpts, handlers.WithOutputFields(&outputFields)) + if outputFields.Has(globals.ScopeField) { + outputOpts = append(outputOpts, handlers.WithScope(authResults.Scope)) + } + if outputFields.Has(globals.AuthorizedActionsField) { + outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authResults.FetchActionSetForId(ctx, t.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(ctx, t, ts, cl, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.RemoveTargetCredentialLibrariesResponse{Item: item}, nil +} + func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSessionRequest) (*pbs.AuthorizeSessionResponse, error) { const op = "targets.(Service).AuthorizeSession" if err := validateAuthorizeSessionRequest(req); err != nil { @@ -485,7 +612,7 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession if err != nil { return nil, err } - t, hostSets, err := repo.LookupTarget(ctx, t.GetPublicId()) + t, hostSets, libs, err := repo.LookupTarget(ctx, t.GetPublicId()) if err != nil { if errors.IsNotFoundError(err) { return nil, handlers.NotFoundErrorf("Target %q not found.", t.GetPublicId()) @@ -653,19 +780,30 @@ HostSetIterationLoop: endpointUrl.Host = endpointHost } + var reqs []credential.Request + var dynCreds []*session.DynamicCredential + for _, l := range libs { + reqs = append(reqs, credential.Request{ + SourceId: l.GetCredentialLibraryId(), + Purpose: credential.Purpose(l.GetCredentialPurpose()), + }) + dynCreds = append(dynCreds, session.NewDynamicCredential(l.GetCredentialLibraryId(), credential.Purpose(l.GetCredentialPurpose()))) + } + expTime := timestamppb.Now() expTime.Seconds += int64(t.GetSessionMaxSeconds()) sessionComposition := session.ComposedOf{ - UserId: authResults.UserId, - HostId: chosenId.hostId, - TargetId: t.GetPublicId(), - HostSetId: chosenId.hostSetId, - AuthTokenId: authResults.AuthTokenId, - ScopeId: authResults.Scope.Id, - Endpoint: endpointUrl.String(), - ExpirationTime: ×tamp.Timestamp{Timestamp: expTime}, - ConnectionLimit: t.GetSessionConnectionLimit(), - WorkerFilter: t.GetWorkerFilter(), + UserId: authResults.UserId, + HostId: chosenId.hostId, + TargetId: t.GetPublicId(), + HostSetId: chosenId.hostSetId, + AuthTokenId: authResults.AuthTokenId, + ScopeId: authResults.Scope.Id, + Endpoint: endpointUrl.String(), + ExpirationTime: ×tamp.Timestamp{Timestamp: expTime}, + ConnectionLimit: t.GetSessionConnectionLimit(), + WorkerFilter: t.GetWorkerFilter(), + DynamicCredentials: dynCreds, } sess, err := session.New(sessionComposition) @@ -681,6 +819,38 @@ HostSetIterationLoop: return nil, err } + var cs []credential.Dynamic + if len(reqs) > 0 { + credRepo, err := s.vaultCredRepoFn() + if err != nil { + return nil, errors.Wrap(err, op) + } + cs, err = credRepo.Issue(ctx, sess.GetPublicId(), reqs) + if err != nil { + return nil, errors.Wrap(err, op) + } + } + + var creds []*pb.SessionCredential + for _, c := range cs { + l := c.Library() + // TODO: Access the json directly from the vault response instead of re-marshalling it. + jSecret, err := json.Marshal(c.Secret()) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("marshalling secret to json")) + } + creds = append(creds, &pb.SessionCredential{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: l.GetPublicId(), + Name: l.GetName(), + Description: l.GetDescription(), + CredentialStoreId: l.GetStoreId(), + Type: credential.SubtypeFromId(l.GetPublicId()).String(), + }, + Secret: base64.StdEncoding.EncodeToString(jSecret), + }) + } + sad := &pb.SessionAuthorizationData{ SessionId: sess.PublicId, TargetId: t.GetPublicId(), @@ -711,29 +881,30 @@ HostSetIterationLoop: HostId: chosenId.hostId, HostSetId: chosenId.hostSetId, Endpoint: endpointUrl.String(), + Credentials: creds, } return &pbs.AuthorizeSessionResponse{Item: ret}, nil } -func (s Service) getFromRepo(ctx context.Context, id string) (target.Target, []*target.TargetSet, error) { +func (s Service) getFromRepo(ctx context.Context, id string) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } - u, m, err := repo.LookupTarget(ctx, id) + u, hs, cl, err := repo.LookupTarget(ctx, id) if err != nil { if errors.IsNotFoundError(err) { - return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) + return nil, nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) } - return nil, nil, err + return nil, nil, nil, err } if u == nil { - return nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) + return nil, nil, nil, handlers.NotFoundErrorf("Target %q doesn't exist.", id) } - return u, m, nil + return u, hs, cl, nil } -func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Target, []*target.TargetSet, error) { +func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { const op = "targets.(Service).createInRepo" opts := []target.Option{target.WithName(item.GetName().GetValue())} if item.GetDescription() != nil { @@ -750,30 +921,30 @@ func (s Service) createInRepo(ctx context.Context, item *pb.Target) (target.Targ } tcpAttrs := &pb.TcpTargetAttributes{} if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.") } if tcpAttrs.GetDefaultPort().GetValue() != 0 { opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue())) } u, err := target.NewTcpTarget(item.GetScopeId(), opts...) if err != nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err) + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for creation: %v.", err) } repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } - out, m, err := repo.CreateTcpTarget(ctx, u) + out, hs, cl, err := repo.CreateTcpTarget(ctx, u) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create target")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create target")) } if out == nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create target but no error returned from repository.") } - return out, m, nil + return out, hs, cl, nil } -func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (target.Target, []*target.TargetSet, error) { +func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []string, item *pb.Target) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { const op = "targets.(Service).updateInRepo" var opts []target.Option if desc := item.GetDescription(); desc != nil { @@ -793,7 +964,7 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st } tcpAttrs := &pb.TcpTargetAttributes{} if err := handlers.StructToProto(item.GetAttributes(), tcpAttrs); err != nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.InvalidArgument, "Provided attributes don't match expected format.") } if tcpAttrs.GetDefaultPort().GetValue() != 0 { opts = append(opts, target.WithDefaultPort(tcpAttrs.GetDefaultPort().GetValue())) @@ -801,25 +972,25 @@ func (s Service) updateInRepo(ctx context.Context, scopeId, id string, mask []st version := item.GetVersion() u, err := target.NewTcpTarget(scopeId, opts...) if err != nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err) + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to build target for update: %v.", err) } u.PublicId = id dbMask := maskManager.Translate(mask) if len(dbMask) == 0 { - return nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."}) + return nil, nil, nil, handlers.InvalidArgumentErrorf("No valid fields included in the update mask.", map[string]string{"update_mask": "No valid paths provided in the update mask."}) } repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } - out, m, rowsUpdated, err := repo.UpdateTcpTarget(ctx, u, version, dbMask) + out, hs, cl, rowsUpdated, err := repo.UpdateTcpTarget(ctx, u, version, dbMask) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update target")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to update target")) } if rowsUpdated == 0 { - return nil, nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id) + return nil, nil, nil, handlers.NotFoundErrorf("Target %q not found or incorrect version provided.", id) } - return out, m, nil + return out, hs, cl, nil } func (s Service) deleteFromRepo(ctx context.Context, id string) (bool, error) { @@ -850,63 +1021,132 @@ func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]target. return ul, nil } -func (s Service) addInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (target.Target, []*target.TargetSet, error) { +func (s Service) addSetsInRepo(ctx context.Context, targetId string, hostSetId []string, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } - out, m, err := repo.AddTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetId, false)) + out, hs, cl, err := repo.AddTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetId, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err) + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add host sets to target: %v.", err) } if out == nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding host sets to it.") } - return out, m, nil + return out, hs, cl, nil } -func (s Service) setInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) { - const op = "targets.(Service).setInRepo" +func (s Service) setSetsInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { + const op = "targets.(Service).setSetsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } - _, _, err = repo.SetTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false)) + _, _, _, err = repo.SetTargetHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err) + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set host sets in target: %v.", err) } - out, m, err := repo.LookupTarget(ctx, targetId) + out, hs, cl, err := repo.LookupTarget(ctx, targetId) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting host sets")) } if out == nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting host sets for it.") } - return out, m, nil + return out, hs, cl, nil } -func (s Service) removeInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, error) { - const op = "targets.(Service).removeInRepo" +func (s Service) removeSetsInRepo(ctx context.Context, targetId string, hostSetIds []string, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { + const op = "targets.(Service).removeSetsInRepo" repo, err := s.repoFn() if err != nil { - return nil, nil, err + return nil, nil, nil, err } _, err = repo.DeleteTargeHostSets(ctx, targetId, version, strutil.RemoveDuplicates(hostSetIds, false)) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err) + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove host sets from target: %v.", err) } - out, m, err := repo.LookupTarget(ctx, targetId) + out, hs, cl, err := repo.LookupTarget(ctx, targetId) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing host sets")) } if out == nil { - return nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.") + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing host sets from it.") } - return out, m, nil + return out, hs, cl, nil +} + +func (s Service) addLibrariesInRepo(ctx context.Context, targetId string, libraryIds []string, libraries []*pbs.CredentialLibrary, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, err + } + lids := make([]string, 0, len(libraryIds)+len(libraries)) + lids = append(lids, libraryIds...) + for _, l := range libraries { + lids = append(lids, l.GetId()) + } + out, hs, cl, err := repo.AddTargetCredentialLibraries(ctx, targetId, version, strutil.RemoveDuplicates(lids, false)) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add credential libraries to target: %v.", err) + } + if out == nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after adding credential libraries to it.") + } + return out, hs, cl, nil +} + +func (s Service) setLibrariesInRepo(ctx context.Context, targetId string, libraryIds []string, libraries []*pbs.CredentialLibrary, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { + const op = "targets.(Service).setLibrariesInRepo" + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, err + } + lids := make([]string, 0, len(libraryIds)+len(libraries)) + lids = append(lids, libraryIds...) + for _, l := range libraries { + lids = append(lids, l.GetId()) + } + _, _, _, err = repo.SetTargetCredentialLibraries(ctx, targetId, version, strutil.RemoveDuplicates(lids, false)) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential libraries in target: %v.", err) + } + + out, hs, cl, err := repo.LookupTarget(ctx, targetId) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after setting credential libraries")) + } + if out == nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after setting credential libraries for it.") + } + return out, hs, cl, nil +} + +func (s Service) removeLibrariesInRepo(ctx context.Context, targetId string, libraryIds []string, version uint32) (target.Target, []*target.TargetSet, []*target.TargetLibrary, error) { + const op = "targets.(Service).removeLibrariesInRepo" + repo, err := s.repoFn() + if err != nil { + return nil, nil, nil, err + } + _, err = repo.DeleteTargetCredentialLibraries(ctx, targetId, version, strutil.RemoveDuplicates(libraryIds, false)) + if err != nil { + // TODO: Figure out a way to surface more helpful error info beyond the Internal error. + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove credential libraries from target: %v.", err) + } + out, hs, cl, err := repo.LookupTarget(ctx, targetId) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to look up target after removing credential libraries")) + } + if out == nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to lookup target after removing credential libraries from it.") + } + return out, hs, cl, nil } func (s Service) authResult(ctx context.Context, id string, a action.Type, lookupOpt ...target.Option) auth.VerifyResults { @@ -938,7 +1178,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type, looku res.Error = err return res } - t, _, err = repo.LookupTarget(ctx, id, lookupOpt...) + t, _, _, err = repo.LookupTarget(ctx, id, lookupOpt...) if err != nil { // TODO: Fix this with new/better error handling if strings.Contains(err.Error(), "more than one row returned by a subquery") { @@ -962,7 +1202,7 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type, looku return ret } -func toProto(ctx context.Context, in target.Target, m []*target.TargetSet, opt ...handlers.Option) (*pb.Target, error) { +func toProto(ctx context.Context, in target.Target, m []*target.TargetSet, l []*target.TargetLibrary, opt ...handlers.Option) (*pb.Target, error) { opts := handlers.GetOpts(opt...) if opts.WithOutputFields == nil { return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building target proto") @@ -1022,6 +1262,20 @@ func toProto(ctx context.Context, in target.Target, m []*target.TargetSet, opt . }) } } + if outputFields.Has(globals.CredentialLibraryIdsField) { + for _, cl := range l { + out.CredentialLibraryIds = append(out.CredentialLibraryIds, cl.GetCredentialLibraryId()) + } + } + if outputFields.Has(globals.CredentialLibrariesField) { + for _, cl := range l { + out.CredentialLibraries = append(out.CredentialLibraries, &pb.CredentialLibrary{ + Id: cl.GetCredentialLibraryId(), + CredentialStoreId: cl.StoreId, + Purpose: cl.GetCredentialPurpose(), + }) + } + } if outputFields.Has(globals.AttributesField) { attrs := &pb.TcpTargetAttributes{} if in.GetDefaultPort() > 0 { @@ -1049,10 +1303,10 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetItem().GetScopeId()), scope.Project.Prefix()) { - badFields["scope_id"] = "This field is required to have a properly formatted project scope id." + badFields[globals.ScopeIdField] = "This field is required to have a properly formatted project scope id." } if req.GetItem().GetName() == nil || req.GetItem().GetName().GetValue() == "" { - badFields["name"] = "This field is required." + badFields[globals.NameField] = "This field is required." } if req.GetItem().GetSessionConnectionLimit() != nil { val := req.GetItem().GetSessionConnectionLimit().GetValue() @@ -1060,17 +1314,17 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { case val == -1: case val > 0: default: - badFields["session_connection_limit"] = "This must be -1 (unlimited) or greater than zero." + badFields[globals.SessionConnectionLimitField] = "This must be -1 (unlimited) or greater than zero." } } if req.GetItem().GetSessionMaxSeconds() != nil && req.GetItem().GetSessionMaxSeconds().GetValue() == 0 { - badFields["session_max_seconds"] = "This must be greater than zero." + badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } switch target.SubtypeFromType(req.GetItem().GetType()) { case target.TcpSubtype: tcpAttrs := &pb.TcpTargetAttributes{} if err := handlers.StructToProto(req.GetItem().GetAttributes(), tcpAttrs); err != nil { - badFields["attributes"] = "Attribute fields do not match the expected format." + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." } if tcpAttrs.GetDefaultPort() != nil && tcpAttrs.GetDefaultPort().GetValue() == 0 { badFields["attributes.default_port"] = "This optional field cannot be set to 0." @@ -1079,13 +1333,13 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { switch req.GetItem().GetType() { case target.TcpTargetType.String(): case "": - badFields["type"] = "This is a required field." + badFields[globals.TypeField] = "This is a required field." default: - badFields["type"] = "Unknown type provided." + badFields[globals.TypeField] = "Unknown type provided." } if filter := req.GetItem().GetWorkerFilter(); filter != nil { if _, err := bexpr.CreateEvaluator(filter.GetValue()); err != nil { - badFields["worker_filter"] = "Unable to successfully parse filter expression." + badFields[globals.WorkerFilterField] = "Unable to successfully parse filter expression." } } return badFields @@ -1095,8 +1349,8 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error { func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { badFields := map[string]string{} - if handlers.MaskContains(req.GetUpdateMask().GetPaths(), "name") && req.GetItem().GetName().GetValue() == "" { - badFields["name"] = "This field cannot be set to empty." + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), globals.NameField) && req.GetItem().GetName().GetValue() == "" { + badFields[globals.NameField] = "This field cannot be set to empty." } if req.GetItem().GetSessionConnectionLimit() != nil { val := req.GetItem().GetSessionConnectionLimit().GetValue() @@ -1104,20 +1358,20 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { case val == -1: case val > 0: default: - badFields["session_connection_limit"] = "This must be -1 (unlimited) or greater than zero." + badFields[globals.SessionConnectionLimitField] = "This must be -1 (unlimited) or greater than zero." } } if req.GetItem().GetSessionMaxSeconds() != nil && req.GetItem().GetSessionMaxSeconds().GetValue() == 0 { - badFields["session_max_seconds"] = "This must be greater than zero." + badFields[globals.SessionMaxSecondsField] = "This must be greater than zero." } switch target.SubtypeFromId(req.GetItem().GetType()) { case target.TcpSubtype: if req.GetItem().GetType() != "" && target.SubtypeFromType(req.GetItem().GetType()) != target.TcpSubtype { - badFields["type"] = "Cannot modify the resource type." + badFields[globals.TypeField] = "Cannot modify the resource type." } tcpAttrs := &pb.TcpTargetAttributes{} if err := handlers.StructToProto(req.GetItem().GetAttributes(), tcpAttrs); err != nil { - badFields["attributes"] = "Attribute fields do not match the expected format." + badFields[globals.AttributesField] = "Attribute fields do not match the expected format." } if tcpAttrs.GetDefaultPort() != nil && tcpAttrs.GetDefaultPort().GetValue() == 0 { badFields["attributes.default_port"] = "This optional field cannot be set to 0." @@ -1125,7 +1379,7 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error { } if filter := req.GetItem().GetWorkerFilter(); filter != nil { if _, err := bexpr.CreateEvaluator(filter.GetValue()); err != nil { - badFields["worker_filter"] = "Unable to successfully parse filter expression." + badFields[globals.WorkerFilterField] = "Unable to successfully parse filter expression." } } return badFields @@ -1140,7 +1394,7 @@ func validateListRequest(req *pbs.ListTargetsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetScopeId()), scope.Project.Prefix()) && !req.GetRecursive() { - badFields["scope_id"] = "This field must be a valid project scope ID or the list operation must be recursive." + badFields[globals.ScopeIdField] = "This field must be a valid project scope ID or the list operation must be recursive." } if _, err := handlers.NewFilter(req.GetFilter()); err != nil { badFields["filter"] = fmt.Sprintf("This field could not be parsed. %v", err) @@ -1151,20 +1405,20 @@ func validateListRequest(req *pbs.ListTargetsRequest) error { return nil } -func validateAddRequest(req *pbs.AddTargetHostSetsRequest) error { +func validateAddSetsRequest(req *pbs.AddTargetHostSetsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } if len(req.GetHostSetIds()) == 0 { - badFields["host_set_ids"] = "Must be non-empty." + badFields[globals.HostSetIdsField] = "Must be non-empty." } for _, id := range req.GetHostSetIds() { if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { - badFields["host_set_ids"] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) + badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) break } } @@ -1174,17 +1428,17 @@ func validateAddRequest(req *pbs.AddTargetHostSetsRequest) error { return nil } -func validateSetRequest(req *pbs.SetTargetHostSetsRequest) error { +func validateSetSetsRequest(req *pbs.SetTargetHostSetsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } for _, id := range req.GetHostSetIds() { if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { - badFields["host_set_ids"] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) + badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) break } } @@ -1194,20 +1448,106 @@ func validateSetRequest(req *pbs.SetTargetHostSetsRequest) error { return nil } -func validateRemoveRequest(req *pbs.RemoveTargetHostSetsRequest) error { +func validateRemoveSetsRequest(req *pbs.RemoveTargetHostSetsRequest) error { badFields := map[string]string{} if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if req.GetVersion() == 0 { - badFields["version"] = "Required field." + badFields[globals.VersionField] = "Required field." } if len(req.GetHostSetIds()) == 0 { - badFields["host_set_ids"] = "Must be non-empty." + badFields[globals.HostSetIdsField] = "Must be non-empty." } for _, id := range req.GetHostSetIds() { if !handlers.ValidId(handlers.Id(id), static.HostSetPrefix) { - badFields["host_set_ids"] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) + badFields[globals.HostSetIdsField] = fmt.Sprintf("Incorrectly formatted host set identifier %q.", id) + break + } + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + return nil +} + +func validateAddLibrariesRequest(req *pbs.AddTargetCredentialLibrariesRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + badFields[globals.IdField] = "Incorrectly formatted identifier." + } + if req.GetVersion() == 0 { + badFields[globals.VersionField] = "Required field." + } + if len(req.GetCredentialLibraryIds())+len(req.GetCredentialLibraries()) == 0 { + badFields[globals.CredentialLibraryIdsField] = "Must be non-empty." + } + for _, id := range req.GetCredentialLibraryIds() { + if !handlers.ValidId(handlers.Id(id), vault.CredentialLibraryPrefix) { + badFields[globals.CredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", id) + break + } + } + for _, cl := range req.GetCredentialLibraries() { + if !handlers.ValidId(handlers.Id(cl.GetId()), vault.CredentialLibraryPrefix) { + badFields[globals.CredentialLibrariesField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", cl.GetId()) + break + } + if cl.GetPurpose() != "" && cl.GetPurpose() != string(credential.ApplicationPurpose) { + badFields[globals.CredentialLibrariesField] = fmt.Sprintf("Unrecognized purpose %q for credential library.", cl.GetPurpose()) + break + } + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + return nil +} + +func validateSetLibrariesRequest(req *pbs.SetTargetCredentialLibrariesRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + badFields[globals.IdField] = "Incorrectly formatted identifier." + } + if req.GetVersion() == 0 { + badFields[globals.VersionField] = "Required field." + } + for _, id := range req.GetCredentialLibraryIds() { + if !handlers.ValidId(handlers.Id(id), vault.CredentialLibraryPrefix) { + badFields[globals.CredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", id) + break + } + } + for _, cl := range req.GetCredentialLibraries() { + if !handlers.ValidId(handlers.Id(cl.GetId()), vault.CredentialLibraryPrefix) { + badFields[globals.CredentialLibrariesField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", cl.GetId()) + break + } + if cl.GetPurpose() != "" && cl.GetPurpose() != string(credential.ApplicationPurpose) { + badFields[globals.CredentialLibrariesField] = fmt.Sprintf("Unrecognized purpose %q for credential library.", cl.GetPurpose()) + break + } + } + if len(badFields) > 0 { + return handlers.InvalidArgumentErrorf("Errors in provided fields.", badFields) + } + return nil +} + +func validateRemoveLibrariesRequest(req *pbs.RemoveTargetCredentialLibrariesRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { + badFields[globals.IdField] = "Incorrectly formatted identifier." + } + if req.GetVersion() == 0 { + badFields[globals.VersionField] = "Required field." + } + if len(req.GetCredentialLibraryIds()) == 0 { + badFields[globals.CredentialLibraryIdsField] = "Must be non-empty." + } + for _, id := range req.GetCredentialLibraryIds() { + if !handlers.ValidId(handlers.Id(id), vault.CredentialLibraryPrefix) { + badFields[globals.CredentialLibraryIdsField] = fmt.Sprintf("Incorrectly formatted credential library identifier %q.", id) break } } @@ -1224,24 +1564,24 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { scopeNameEmpty := req.GetScopeName() == "" if nameEmpty { if !handlers.ValidId(handlers.Id(req.GetId()), target.TcpTargetPrefix) { - badFields["id"] = "Incorrectly formatted identifier." + badFields[globals.IdField] = "Incorrectly formatted identifier." } if !scopeIdEmpty { - badFields["scope_id"] = "Scope ID provided when target name was empty." + badFields[globals.ScopeIdField] = "Scope ID provided when target name was empty." } if !scopeNameEmpty { - badFields["scope_id"] = "Scope name provided when target name was empty." + badFields[globals.ScopeIdField] = "Scope name provided when target name was empty." } } else { if req.GetName() != req.GetId() { - badFields["name"] = "Target name provided but does not match the given ID value from the URL." + badFields[globals.NameField] = "Target name provided but does not match the given ID value from the URL." } switch { case scopeIdEmpty && scopeNameEmpty: - badFields["scope_id"] = "Scope ID or scope name must be provided when target name is used." + badFields[globals.ScopeIdField] = "Scope ID or scope name must be provided when target name is used." badFields["scope_name"] = "Scope ID or scope name must be provided when target name is used." case !scopeIdEmpty && !scopeNameEmpty: - badFields["scope_id"] = "Scope ID and scope name cannot both be provided when target name is used." + badFields[globals.ScopeIdField] = "Scope ID and scope name cannot both be provided when target name is used." badFields["scope_name"] = "Scope ID and scope name cannot both be provided when target name is used." } } @@ -1249,7 +1589,7 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { switch host.SubtypeFromId(req.GetHostId()) { case host.StaticSubtype: default: - badFields["host_id"] = "Incorrectly formatted identifier." + badFields[globals.HostIdField] = "Incorrectly formatted identifier." } } if len(badFields) > 0 { diff --git a/internal/servers/controller/handlers/targets/target_service_test.go b/internal/servers/controller/handlers/targets/target_service_test.go index fb0a4d99f..422986935 100644 --- a/internal/servers/controller/handlers/targets/target_service_test.go +++ b/internal/servers/controller/handlers/targets/target_service_test.go @@ -2,26 +2,42 @@ package targets_test import ( "context" + "encoding/base64" + "encoding/json" "errors" "fmt" + "path" + "sort" "strings" + "sync" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" + credpb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/credentiallibraries" "github.com/hashicorp/boundary/internal/gen/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/internal/gen/controller/api/resources/targets" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + spbs "github.com/hashicorp/boundary/internal/gen/controller/servers/services" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/requests" + "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/servers" + spb "github.com/hashicorp/boundary/internal/servers" "github.com/hashicorp/boundary/internal/servers/controller/handlers" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/credentiallibraries" "github.com/hashicorp/boundary/internal/servers/controller/handlers/targets" + "github.com/hashicorp/boundary/internal/servers/controller/handlers/workers" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/types/scope" + "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping" "github.com/jinzhu/gorm" "github.com/stretchr/testify/assert" @@ -36,10 +52,23 @@ import ( "google.golang.org/protobuf/types/known/wrapperspb" ) -var testAuthorizedActions = []string{"no-op", "read", "update", "delete", "add-host-sets", "set-host-sets", "remove-host-sets", "authorize-session"} +var testAuthorizedActions = []string{ + "no-op", + "read", + "update", + "delete", + "add-host-sets", + "set-host-sets", + "remove-host-sets", + "add-credential-libraries", + "set-credential-libraries", + "remove-credential-libraries", + "authorize-session", +} func testService(t *testing.T, conn *gorm.DB, kms *kms.Kms, wrapper wrapping.Wrapper) (targets.Service, error) { rw := db.New(conn) + sche := scheduler.TestScheduler(t, conn, wrapper) repoFn := func() (*target.Repository, error) { return target.NewRepository(rw, rw, kms) } @@ -55,7 +84,10 @@ func testService(t *testing.T, conn *gorm.DB, kms *kms.Kms, wrapper wrapping.Wra staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(rw, rw, kms) } - return targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn) + credentialRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + return targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) } func TestGet(t *testing.T) { @@ -534,7 +566,7 @@ func TestUpdate(t *testing.T) { tar, err := target.NewTcpTarget(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) tar.DefaultPort = 2 require.NoError(t, err) - gtar, _, err := repo.CreateTcpTarget(context.Background(), tar, target.WithHostSets([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) + gtar, _, _, err := repo.CreateTcpTarget(context.Background(), tar, target.WithHostSets([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) require.NoError(t, err) tar = gtar.(*target.TcpTarget) @@ -542,7 +574,7 @@ func TestUpdate(t *testing.T) { resetTarget := func() { version++ - _, _, _, err = repo.UpdateTcpTarget(context.Background(), tar, version, []string{"Name", "Description"}) + _, _, _, _, err = repo.UpdateTcpTarget(context.Background(), tar, version, []string{"Name", "Description"}) require.NoError(t, err, "Failed to reset target.") version++ } @@ -892,7 +924,7 @@ func TestUpdate_BadVersion(t *testing.T) { tar, err := target.NewTcpTarget(proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) tar.DefaultPort = 2 require.NoError(t, err) - gtar, _, err := repo.CreateTcpTarget(context.Background(), tar) + gtar, _, _, err := repo.CreateTcpTarget(context.Background(), tar) require.NoError(t, err) tested, err := testService(t, conn, kms, wrapper) @@ -1264,3 +1296,828 @@ func TestRemoveTargetHostSets(t *testing.T) { }) } } + +func TestAddTargetLibraries(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + _, proj := iam.TestScopes(t, iamRepo) + + s, err := testService(t, conn, kms, wrapper) + require.NoError(t, err, "Error when getting new target service.") + + store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + + addCases := []struct { + name string + tar *target.TcpTarget + addLibraryIds []string + addLibraries []*pbs.CredentialLibrary + resultLibraryIds []string + }{ + { + name: "Add set on empty target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + addLibraryIds: []string{cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Add library on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + addLibraryIds: []string{cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + { + name: "Add duplicated libraries on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + addLibraryIds: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + { + name: "Add library on empty target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty for libraries"), + addLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Add library on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated for libraries", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + addLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + { + name: "Add duplicated libraries on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated for libraries", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + addLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}, {Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + { + name: "Add duplicated libraries across 2 fields on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicated mixed", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + addLibraryIds: []string{cls[1].GetPublicId()}, + addLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + } + + for _, tc := range addCases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.AddTargetCredentialLibrariesRequest{ + Id: tc.tar.GetPublicId(), + Version: tc.tar.GetVersion(), + CredentialLibraryIds: tc.addLibraryIds, + CredentialLibraries: tc.addLibraries, + } + + got, err := s.AddTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + require.NoError(t, err, "Got error: %v", s) + + assert.ElementsMatch(t, tc.resultLibraryIds, got.GetItem().GetCredentialLibraryIds()) + + assert.Equal(t, len(tc.resultLibraryIds), len(got.GetItem().GetCredentialLibraries())) + + wantTemplate := &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Purpose: string(credential.ApplicationPurpose), + } + for _, cl := range got.GetItem().GetCredentialLibraries() { + cl.Id = "" + assert.Empty(t, cmp.Diff(wantTemplate, cl, protocmp.Transform())) + } + }) + } + + tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + + failCases := []struct { + name string + req *pbs.AddTargetCredentialLibrariesRequest + err error + }{ + { + name: "Bad library Id", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: "bad id", + Version: tar.GetVersion(), + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Bad version", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion() + 2, + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Empty library list", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Incorrect library ids", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraryIds: []string{"incorrect"}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Incorrect library id in libraries", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraries: []*pbs.CredentialLibrary{{Id: "incorrect"}}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Incorrect purpose in libraries", + req: &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraries: []*pbs.CredentialLibrary{{Id: cls[0].GetPublicId(), Purpose: "Wrong"}}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.AddTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "AddTargetCredentialLibraries(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + }) + } +} + +func TestSetTargetLibraries(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + _, proj := iam.TestScopes(t, iamRepo) + + s, err := testService(t, conn, kms, wrapper) + require.NoError(t, err, "Error when getting new target service.") + + store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + + resultingTargetLibrary := func(id string) *pb.CredentialLibrary { + return &pb.CredentialLibrary{ + Id: id, + CredentialStoreId: store.GetPublicId(), + Purpose: string(credential.ApplicationPurpose), + } + } + + setCases := []struct { + name string + tar *target.TcpTarget + setLibraryIds []string + setLibraries []*pbs.CredentialLibrary + resultLibraryIds []string + resultLibraries []*pb.CredentialLibrary + }{ + { + name: "Set on empty target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + setLibraryIds: []string{cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Set on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + setLibraryIds: []string{cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Set duplicate libraries on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + setLibraryIds: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Set duplicate libraries on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "duplicate with libraries", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + setLibraryIds: []string{cls[1].GetPublicId()}, + setLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Set across libraries and library", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "across fields"), + setLibraryIds: []string{cls[0].GetPublicId()}, + setLibraries: []*pbs.CredentialLibrary{{Id: cls[1].GetPublicId()}}, + resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + resultLibraries: []*pb.CredentialLibrary{resultingTargetLibrary(cls[0].GetPublicId()), resultingTargetLibrary(cls[1].GetPublicId())}, + }, + { + name: "Set empty on populated target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]string{cls[0].GetPublicId()})), + setLibraryIds: []string{}, + resultLibraryIds: nil, + }, + } + for _, tc := range setCases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.SetTargetCredentialLibrariesRequest{ + Id: tc.tar.GetPublicId(), + Version: tc.tar.GetVersion(), + CredentialLibraryIds: tc.setLibraryIds, + CredentialLibraries: tc.setLibraries, + } + + got, err := s.SetTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + require.NoError(t, err, "Got error: %v", s) + assert.ElementsMatch(t, tc.resultLibraryIds, got.GetItem().GetCredentialLibraryIds()) + + if len(tc.resultLibraries) != 0 { + sort.Slice(tc.resultLibraries, func(i, j int) bool { + return tc.resultLibraries[i].GetId() < tc.resultLibraries[j].GetId() + }) + sort.Slice(got.GetItem().CredentialLibraries, func(i, j int) bool { + return got.GetItem().CredentialLibraries[i].GetId() < got.GetItem().CredentialLibraries[j].GetId() + }) + assert.Empty(t, cmp.Diff(tc.resultLibraries, got.GetItem().GetCredentialLibraries(), protocmp.Transform())) + } else { + assert.Equal(t, len(tc.resultLibraryIds), len(got.GetItem().GetCredentialLibraries())) + for _, cl := range got.GetItem().GetCredentialLibraries() { + assert.Empty(t, cmp.Diff(resultingTargetLibrary(cl.Id), cl, protocmp.Transform())) + } + } + }) + } + + tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test name") + + failCases := []struct { + name string + req *pbs.SetTargetCredentialLibrariesRequest + err error + }{ + { + name: "Bad target Id", + req: &pbs.SetTargetCredentialLibrariesRequest{ + Id: "bad id", + Version: tar.GetVersion(), + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Bad version", + req: &pbs.SetTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion() + 3, + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Bad library id", + req: &pbs.SetTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraryIds: []string{"invalid"}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Incorrect library id in libraries", + req: &pbs.SetTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraries: []*pbs.CredentialLibrary{{Id: "incorrect"}}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Incorrect purpose in libraries", + req: &pbs.SetTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraries: []*pbs.CredentialLibrary{{Id: cls[0].GetPublicId(), Purpose: "Wrong"}}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.SetTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "SetTargetCredentialLibraries(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + }) + } +} + +func TestRemoveTargetLibraries(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + _, proj := iam.TestScopes(t, iamRepo) + + s, err := testService(t, conn, kms, wrapper) + require.NoError(t, err, "Error when getting new target service.") + + store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + + removeCases := []struct { + name string + tar *target.TcpTarget + removeLibs []string + resultLibs []string + wantErr bool + }{ + { + name: "Remove from empty", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "empty"), + removeLibs: []string{cls[1].GetPublicId()}, + wantErr: true, + }, + { + name: "Remove 1 of 2 libraries", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialLibraries([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + removeLibs: []string{cls[1].GetPublicId()}, + resultLibs: []string{cls[0].GetPublicId()}, + }, + { + name: "Remove 1 duplicate set of 2 libraries", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialLibraries([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + removeLibs: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, + resultLibs: []string{cls[0].GetPublicId()}, + }, + { + name: "Remove all libraries from target", + tar: target.TestTcpTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialLibraries([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + removeLibs: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + resultLibs: []string{}, + }, + } + + for _, tc := range removeCases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.RemoveTargetCredentialLibrariesRequest{ + Id: tc.tar.GetPublicId(), + Version: tc.tar.GetVersion(), + CredentialLibraryIds: tc.removeLibs, + } + + got, err := s.RemoveTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), req) + if tc.wantErr { + assert.Error(t, err) + return + } + require.NoError(t, err, "Got error: %v", s) + + assert.ElementsMatch(t, tc.resultLibs, got.GetItem().GetCredentialLibraryIds()) + }) + } + + tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "testing") + + failCases := []struct { + name string + req *pbs.RemoveTargetCredentialLibrariesRequest + err error + }{ + { + name: "Bad version", + req: &pbs.RemoveTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion() + 3, + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.Internal), + }, + { + name: "Bad target Id", + req: &pbs.RemoveTargetCredentialLibrariesRequest{ + Id: "bad id", + Version: tar.GetVersion(), + CredentialLibraryIds: []string{cls[0].GetPublicId()}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "empty libraries", + req: &pbs.RemoveTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraryIds: []string{}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid library ids", + req: &pbs.RemoveTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + CredentialLibraryIds: []string{"invalid"}, + }, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range failCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + _, gErr := s.RemoveTargetCredentialLibraries(auth.DisabledAuthTestContext(iamRepoFn, proj.GetPublicId()), tc.req) + if tc.err != nil { + require.Error(gErr) + assert.True(errors.Is(gErr, tc.err), "RemoveTargetCredentialLibraries(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) + } + }) + } +} + +func TestAuthorizeSession(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + sche := scheduler.TestScheduler(t, conn, wrapper) + repoFn := func() (*target.Repository, error) { + return target.NewRepository(rw, rw, kms) + } + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + serversRepoFn := func() (*servers.Repository, error) { + return servers.NewRepository(rw, rw, kms) + } + sessionRepoFn := func() (*session.Repository, error) { + return session.NewRepository(rw, rw, kms) + } + staticHostRepoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + credentialRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + atRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, kms) + } + + org, proj := iam.TestScopes(t, iamRepo) + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + ctx := auth.NewVerifierContext(requests.NewRequestContext(context.Background()), + nil, + iamRepoFn, + atRepoFn, + serversRepoFn, + kms, + auth.RequestInfo{ + Token: at.GetToken(), + TokenFormat: auth.AuthTokenTypeBearer, + PublicId: at.GetPublicId(), + }) + + r := iam.TestRole(t, conn, proj.GetPublicId()) + _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) + _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") + + s, err := targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) + require.NoError(t, err) + + tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), "test") + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + + apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + HostSetIds: []string{hs.GetPublicId()}, + }) + require.NoError(t, err) + + // Tell our DB that there is a worker ready to serve the data + workerService := workers.NewWorkerServiceServer(hclog.Default(), serversRepoFn, sessionRepoFn, &sync.Map{}, kms) + _, err = workerService.Status(ctx, &spbs.StatusRequest{ + Worker: &spb.Server{ + PrivateId: "testworker", + Address: "localhost:8457", + }, + }) + require.NoError(t, err) + + v := vault.NewTestVaultServer(t) + v.MountPKI(t) + sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "pki"})) + + store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) + credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + require.NoError(t, err) + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Name: wrapperspb.String("Library Name"), + Description: wrapperspb.String("Library Description"), + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "path": structpb.NewStringValue(path.Join("pki", "issue", "boundary")), + "http_method": structpb.NewStringValue("POST"), + "http_request_body": structpb.NewStringValue(`{"common_name":"boundary.com"}`), + }}, + }}) + require.NoError(t, err) + + _, err = s.AddTargetCredentialLibraries(ctx, + &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + CredentialLibraryIds: []string{clsResp.GetItem().GetId()}, + Version: apiTar.GetItem().GetVersion(), + }) + require.NoError(t, err) + + asRes1, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ + Id: tar.GetPublicId(), + }) + require.NoError(t, err) + asRes2, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ + Id: tar.GetPublicId(), + }) + require.NoError(t, err) + assert.NotEmpty(t, cmp.Diff(asRes1.GetItem().GetCredentials(), asRes2.GetItem().GetCredentials(), protocmp.Transform()), + "the credentials aren't unique per request authorized session") + + want := &pb.SessionAuthorization{ + Scope: &scopes.ScopeInfo{ + Id: proj.GetPublicId(), + Type: proj.GetType(), + Name: proj.GetName(), + Description: proj.GetDescription(), + ParentScopeId: proj.GetParentId(), + }, + TargetId: tar.GetPublicId(), + UserId: at.GetIamUserId(), + HostSetId: hs.GetPublicId(), + HostId: h.GetPublicId(), + Type: "tcp", + Endpoint: fmt.Sprintf("tcp://%s", h.GetAddress()), + Credentials: []*pb.SessionCredential{{ + CredentialLibrary: &pb.CredentialLibrary{ + Id: clsResp.GetItem().GetId(), + Name: clsResp.GetItem().GetName().GetValue(), + Description: clsResp.GetItem().GetDescription().GetValue(), + CredentialStoreId: store.GetPublicId(), + Type: credential.VaultSubtype.String(), + }, + }}, + // TODO: validate the contents of the authorization token is what is expected + } + wantSecret := map[string]interface{}{ + "certificate": "-----BEGIN CERTIFICATE-----\n", + "issuing_ca": "-----BEGIN CERTIFICATE-----\n", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\n", + "private_key_type": "rsa", + } + _ = wantSecret + got := asRes1.GetItem() + + require.Len(t, got.GetCredentials(), 1) + + gotCred := got.Credentials[0] + assert.NotEmpty(t, gotCred.Secret) + dSec := decodeJsonSecret(t, gotCred.Secret) + require.NoError(t, err) + for k, v := range wantSecret { + gotV, ok := dSec[k] + require.True(t, ok) + assert.Truef(t, strings.HasPrefix(gotV.(string), v.(string)), "%q:%q doesn't have prefix %q", k, gotV, v) + } + gotCred.Secret = "" + + got.AuthorizationToken, got.SessionId, got.CreatedTime = "", "", nil + assert.Empty(t, cmp.Diff(got, want, protocmp.Transform())) +} + +func TestAuthorizeSession_Errors(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + kms := kms.TestKms(t, conn, wrapper) + + sche := scheduler.TestScheduler(t, conn, wrapper) + repoFn := func() (*target.Repository, error) { + return target.NewRepository(rw, rw, kms) + } + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + serversRepoFn := func() (*servers.Repository, error) { + return servers.NewRepository(rw, rw, kms) + } + sessionRepoFn := func() (*session.Repository, error) { + return session.NewRepository(rw, rw, kms) + } + staticHostRepoFn := func() (*static.Repository, error) { + return static.NewRepository(rw, rw, kms) + } + credentialRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + atRepoFn := func() (*authtoken.Repository, error) { + return authtoken.NewRepository(rw, rw, kms) + } + org, proj := iam.TestScopes(t, iamRepo) + + s, err := targets.NewService(kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, staticHostRepoFn, credentialRepoFn) + require.NoError(t, err) + + // Authorized user gets full permissions + at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) + ctx := auth.NewVerifierContext(requests.NewRequestContext(context.Background()), + nil, + iamRepoFn, + atRepoFn, + serversRepoFn, + kms, + auth.RequestInfo{ + Token: at.GetToken(), + TokenFormat: auth.AuthTokenTypeBearer, + PublicId: at.GetPublicId(), + }) + r := iam.TestRole(t, conn, proj.GetPublicId()) + _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) + _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*") + + v := vault.NewTestVaultServer(t, vault.WithDockerNetwork(true)) + v.MountDatabase(t) + sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"})) + store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) + + workerExists := func(tar *target.TcpTarget) (version uint32) { + workerService := workers.NewWorkerServiceServer(hclog.Default(), serversRepoFn, sessionRepoFn, &sync.Map{}, kms) + _, err := workerService.Status(context.Background(), &spbs.StatusRequest{ + Worker: &spb.Server{ + PrivateId: "testworker", + Address: "localhost:123", + }, + }) + require.NoError(t, err) + return tar.GetVersion() + } + + hostSetNoHostExists := func(tar *target.TcpTarget) (version uint32) { + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + + tr, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + HostSetIds: []string{hs.GetPublicId()}, + }) + require.NoError(t, err) + return tr.GetItem().GetVersion() + } + + hostExists := func(tar *target.TcpTarget) (version uint32) { + hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] + h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] + hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] + _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) + apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{ + Id: tar.GetPublicId(), + Version: tar.GetVersion(), + HostSetIds: []string{hs.GetPublicId()}, + }) + require.NoError(t, err) + return apiTar.GetItem().GetVersion() + } + + libraryExists := func(tar *target.TcpTarget) (version uint32) { + credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + require.NoError(t, err) + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "path": structpb.NewStringValue(path.Join("database", "creds", "opened")), + }}, + }}) + require.NoError(t, err) + + tr, err := s.AddTargetCredentialLibraries(ctx, + &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + CredentialLibraryIds: []string{clsResp.GetItem().GetId()}, + Version: tar.GetVersion(), + }) + require.NoError(t, err) + return tr.GetItem().GetVersion() + } + + misConfiguredlibraryExists := func(tar *target.TcpTarget) (version uint32) { + credService, err := credentiallibraries.NewService(credentialRepoFn, iamRepoFn) + require.NoError(t, err) + clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credpb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), + Attributes: &structpb.Struct{Fields: map[string]*structpb.Value{ + "path": structpb.NewStringValue("bad path"), + }}, + }}) + require.NoError(t, err) + + tr, err := s.AddTargetCredentialLibraries(ctx, + &pbs.AddTargetCredentialLibrariesRequest{ + Id: tar.GetPublicId(), + CredentialLibraryIds: []string{clsResp.GetItem().GetId()}, + Version: tar.GetVersion(), + }) + require.NoError(t, err) + return tr.GetItem().GetVersion() + } + + cases := []struct { + name string + setup []func(*target.TcpTarget) uint32 + err bool + }{ + { + // This one must be run first since it relies on the DB not having any worker details + name: "no worker", + setup: []func(tcpTarget *target.TcpTarget) uint32{hostExists, libraryExists}, + err: true, + }, + { + name: "success", + setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostExists, libraryExists}, + }, + { + name: "no hosts", + setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostSetNoHostExists, libraryExists}, + err: true, + }, + { + name: "bad library configuration", + setup: []func(tcpTarget *target.TcpTarget) uint32{workerExists, hostExists, misConfiguredlibraryExists}, + err: true, + }, + } + for i, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + tar := target.TestTcpTarget(t, conn, proj.GetPublicId(), fmt.Sprintf("test-%d", i)) + + for _, fn := range tc.setup { + ver := fn(tar) + tar.Version = ver + } + + res, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ + Id: tar.GetPublicId(), + }) + if tc.err { + require.Error(t, err) + require.Nil(t, res) + return + } + require.NoError(t, err) + require.NotNil(t, res) + }) + } +} + +func decodeJsonSecret(t *testing.T, in string) map[string]interface{} { + t.Helper() + ret := make(map[string]interface{}) + dec := json.NewDecoder(base64.NewDecoder(base64.StdEncoding, strings.NewReader(in))) + dec.UseNumber() + require.NoError(t, dec.Decode(&ret)) + return ret +} diff --git a/internal/servers/controller/handlers/users/user_service.go b/internal/servers/controller/handlers/users/user_service.go index e5aaba272..7ad299734 100644 --- a/internal/servers/controller/handlers/users/user_service.go +++ b/internal/servers/controller/handlers/users/user_service.go @@ -51,7 +51,7 @@ var ( func init() { var err error - if maskManager, err = handlers.NewMaskManager(&store.User{}, &pb.User{}); err != nil { + if maskManager, err = handlers.NewMaskManager(handlers.MaskDestination{&store.User{}}, handlers.MaskSource{&pb.User{}}); err != nil { panic(err) } } diff --git a/internal/session/dynamic_credential.go b/internal/session/dynamic_credential.go new file mode 100644 index 000000000..264e36fca --- /dev/null +++ b/internal/session/dynamic_credential.go @@ -0,0 +1,52 @@ +package session + +import ( + "github.com/hashicorp/boundary/internal/credential" +) + +// A DynamicCredential represents the relationship between a session, a +// credential, and the credential library where the credential was +// retrieved plus the purpose of the credential. +type DynamicCredential struct { + SessionId string `json:"session_id,omitempty" gorm:"primary_key"` + LibraryId string `json:"library_id,omitempty" gorm:"primary_key"` + CredentialPurpose string `json:"credential_purpose,omitempty" gorm:"primary_key"` + CredentialId string `json:"credential_id,omitempty" gorm:"default:null"` + + tableName string `gorm:"-"` +} + +// NewDynamicCredential creates a new in memory Credential representing the +// relationship between session and a credential library. +func NewDynamicCredential(libraryId string, purpose credential.Purpose) *DynamicCredential { + return &DynamicCredential{ + LibraryId: libraryId, + CredentialPurpose: string(purpose), + } +} + +func allocCredential() *DynamicCredential { + return &DynamicCredential{} +} + +func (c *DynamicCredential) clone() *DynamicCredential { + return &DynamicCredential{ + SessionId: c.SessionId, + LibraryId: c.LibraryId, + CredentialPurpose: c.CredentialPurpose, + CredentialId: c.CredentialId, + } +} + +// TableName returns the table name. +func (c *DynamicCredential) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "session_credential_dynamic" +} + +// SetTableName sets the table name. +func (c *DynamicCredential) SetTableName(n string) { + c.tableName = n +} diff --git a/internal/session/repository_session.go b/internal/session/repository_session.go index 1acac4c11..9cb3d0289 100644 --- a/internal/session/repository_session.go +++ b/internal/session/repository_session.go @@ -85,9 +85,18 @@ func (r *Repository) CreateSession(ctx context.Context, sessionWrapper wrapping. db.ExpBackoff{}, func(read db.Reader, w db.Writer) error { returnedSession = newSession.Clone().(*Session) + returnedSession.DynamicCredentials = nil if err = w.Create(ctx, returnedSession); err != nil { return errors.Wrap(err, op) } + for _, cred := range newSession.DynamicCredentials { + cred.SessionId = newSession.PublicId + returnedCred := cred.clone() + if err = w.Create(ctx, returnedCred); err != nil { + return errors.Wrap(err, op) + } + returnedSession.DynamicCredentials = append(returnedSession.DynamicCredentials, returnedCred) + } var foundStates []*State // trigger will create new "Pending" state if foundStates, err = fetchStates(ctx, read, returnedSession.PublicId); err != nil { @@ -136,6 +145,14 @@ func (r *Repository) LookupSession(ctx context.Context, sessionId string, _ ...O return errors.Wrap(err, op) } session.States = states + + var creds []*DynamicCredential + if err := read.SearchWhere(ctx, &creds, "session_id = ?", []interface{}{sessionId}); err != nil { + return errors.Wrap(err, op) + } + if len(creds) > 0 { + session.DynamicCredentials = creds + } return nil }, ) diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index 836d86019..c049e1ff2 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -8,12 +8,16 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" authtokenStore "github.com/hashicorp/boundary/internal/authtoken/store" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" "github.com/hashicorp/boundary/internal/host/static" staticStore "github.com/hashicorp/boundary/internal/host/static/store" "github.com/hashicorp/boundary/internal/target" targetStore "github.com/hashicorp/boundary/internal/target/store" + wrapping "github.com/hashicorp/go-kms-wrapping" + "github.com/jinzhu/gorm" "github.com/lib/pq" "google.golang.org/protobuf/types/known/timestamppb" @@ -268,6 +272,13 @@ func TestRepository_CreateSession(t *testing.T) { }, wantErr: false, }, + { + name: "valid-with-credentials", + args: args{ + composedOf: testSessionCredentialParams(t, conn, wrapper, iamRepo), + }, + wantErr: false, + }, { name: "empty-userId", args: args{ @@ -345,15 +356,16 @@ func TestRepository_CreateSession(t *testing.T) { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) s := &Session{ - UserId: tt.args.composedOf.UserId, - HostId: tt.args.composedOf.HostId, - TargetId: tt.args.composedOf.TargetId, - HostSetId: tt.args.composedOf.HostSetId, - AuthTokenId: tt.args.composedOf.AuthTokenId, - ScopeId: tt.args.composedOf.ScopeId, - Endpoint: "tcp://127.0.0.1:22", - ExpirationTime: tt.args.composedOf.ExpirationTime, - ConnectionLimit: tt.args.composedOf.ConnectionLimit, + UserId: tt.args.composedOf.UserId, + HostId: tt.args.composedOf.HostId, + TargetId: tt.args.composedOf.TargetId, + HostSetId: tt.args.composedOf.HostSetId, + AuthTokenId: tt.args.composedOf.AuthTokenId, + ScopeId: tt.args.composedOf.ScopeId, + Endpoint: "tcp://127.0.0.1:22", + ExpirationTime: tt.args.composedOf.ExpirationTime, + ConnectionLimit: tt.args.composedOf.ConnectionLimit, + DynamicCredentials: tt.args.composedOf.DynamicCredentials, } ses, privKey, err := repo.CreateSession(context.Background(), wrapper, s) if tt.wantErr { @@ -370,6 +382,7 @@ func TestRepository_CreateSession(t *testing.T) { assert.NotNil(ses.States[0].StartTime) assert.Equal(ses.States[0].Status, StatusPending) assert.Equal(wrapper.KeyID(), ses.KeyId) + assert.Len(ses.DynamicCredentials, len(s.DynamicCredentials)) foundSession, _, err := repo.LookupSession(context.Background(), ses.PublicId) assert.NoError(err) assert.Equal(wrapper.KeyID(), foundSession.KeyId) @@ -378,13 +391,20 @@ func TestRepository_CreateSession(t *testing.T) { assert.True(foundSession.ExpirationTime.Timestamp.AsTime().Sub(ses.ExpirationTime.Timestamp.AsTime()) < time.Second) ses.ExpirationTime = foundSession.ExpirationTime - assert.Equal(foundSession, ses) + assert.Equal(ses, foundSession) err = db.TestVerifyOplog(t, rw, ses.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) assert.Error(err) require.Equal(1, len(foundSession.States)) assert.Equal(foundSession.States[0].Status, StatusPending) + assert.Equal(s.DynamicCredentials, foundSession.DynamicCredentials) + for _, cred := range foundSession.DynamicCredentials { + assert.Empty(cred.CredentialId) + assert.NotEmpty(cred.SessionId) + assert.NotEmpty(cred.LibraryId) + assert.NotEmpty(cred.CredentialPurpose) + } }) } } @@ -1621,3 +1641,30 @@ func TestRepository_DeleteSession(t *testing.T) { }) } } + +func testSessionCredentialParams(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, iamRepo *iam.Repository) ComposedOf { + t.Helper() + params := TestSessionParams(t, conn, wrapper, iamRepo) + require := require.New(t) + rw := db.New(conn) + + ctx := context.Background() + stores := vault.TestCredentialStores(t, conn, wrapper, params.ScopeId, 1) + libs := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) + + kms := kms.TestKms(t, conn, wrapper) + targetRepo, err := target.NewRepository(rw, rw, kms) + require.NoError(err) + tcpTarget, _, _, err := targetRepo.LookupTarget(ctx, params.TargetId) + require.NoError(err) + require.NotNil(tcpTarget) + _, _, _, err = targetRepo.AddTargetCredentialLibraries(ctx, tcpTarget.GetPublicId(), tcpTarget.GetVersion(), []string{libs[0].PublicId, libs[1].PublicId}) + require.NoError(err) + creds := []*DynamicCredential{ + NewDynamicCredential(libs[0].GetPublicId(), credential.ApplicationPurpose), + NewDynamicCredential(libs[0].GetPublicId(), credential.IngressPurpose), + NewDynamicCredential(libs[1].GetPublicId(), credential.EgressPurpose), + } + params.DynamicCredentials = creds + return params +} diff --git a/internal/session/session.go b/internal/session/session.go index 9d172a39c..8fedf3d3b 100644 --- a/internal/session/session.go +++ b/internal/session/session.go @@ -49,6 +49,9 @@ type ComposedOf struct { // existed at creation time. Round tripping it through here saves a lookup // in the DB. It is not stored in the warehouse. WorkerFilter string + // DynamicCredentials are dynamic credentials that will be retrieved + // for the session. DynamicCredentials optional. + DynamicCredentials []*DynamicCredential } // Session contains information about a user's session with a target @@ -104,8 +107,12 @@ type Session struct { // States for the session which are for read only and are ignored during // write operations - States []*State `gorm:"-"` - tableName string `gorm:"-"` + States []*State `gorm:"-"` + + // DynamicCredentials for the session. + DynamicCredentials []*DynamicCredential `gorm:"-"` + + tableName string `gorm:"-"` } func (s *Session) GetPublicId() string { @@ -121,16 +128,17 @@ var ( func New(c ComposedOf, _ ...Option) (*Session, error) { const op = "session.New" s := Session{ - UserId: c.UserId, - HostId: c.HostId, - TargetId: c.TargetId, - HostSetId: c.HostSetId, - AuthTokenId: c.AuthTokenId, - ScopeId: c.ScopeId, - Endpoint: c.Endpoint, - ExpirationTime: c.ExpirationTime, - ConnectionLimit: c.ConnectionLimit, - WorkerFilter: c.WorkerFilter, + UserId: c.UserId, + HostId: c.HostId, + TargetId: c.TargetId, + HostSetId: c.HostSetId, + AuthTokenId: c.AuthTokenId, + ScopeId: c.ScopeId, + Endpoint: c.Endpoint, + ExpirationTime: c.ExpirationTime, + ConnectionLimit: c.ConnectionLimit, + WorkerFilter: c.WorkerFilter, + DynamicCredentials: c.DynamicCredentials, } if err := s.validateNewSession(); err != nil { return nil, errors.Wrap(err, op) @@ -169,6 +177,13 @@ func (s *Session) Clone() interface{} { clone.States = append(clone.States, cp) } } + if len(s.DynamicCredentials) > 0 { + clone.DynamicCredentials = make([]*DynamicCredential, 0, len(s.DynamicCredentials)) + for _, sc := range s.DynamicCredentials { + cp := sc.clone() + clone.DynamicCredentials = append(clone.DynamicCredentials, cp) + } + } if s.TofuToken != nil { clone.TofuToken = make([]byte, len(s.TofuToken)) copy(clone.TofuToken, s.TofuToken) @@ -252,6 +267,8 @@ func (s *Session) VetForWrite(_ context.Context, _ db.Reader, opType db.OpType, return errors.New(errors.InvalidParameter, op, "connection limit is immutable") case contains(opts.WithFieldMaskPaths, "WorkerFilter"): return errors.New(errors.InvalidParameter, op, "worker filter is immutable") + case contains(opts.WithFieldMaskPaths, "DynamicCredentials"): + return errors.New(errors.InvalidParameter, op, "dynamic credentials are immutable") case contains(opts.WithFieldMaskPaths, "TerminationReason"): if _, err := convertToReason(s.TerminationReason); err != nil { return errors.Wrap(err, op) diff --git a/internal/session/session_test.go b/internal/session/session_test.go index 8db352155..ee391c9ee 100644 --- a/internal/session/session_test.go +++ b/internal/session/session_test.go @@ -20,7 +20,7 @@ func TestSession_Create(t *testing.T) { wrapper := db.TestWrapper(t) iamRepo := iam.TestRepo(t, conn, wrapper) - composedOf := TestSessionParams(t, conn, wrapper, iamRepo) + composedOf := testSessionCredentialParams(t, conn, wrapper, iamRepo) exp := ×tamp.Timestamp{Timestamp: timestamppb.New(time.Now().Add(time.Hour))} type args struct { @@ -43,15 +43,16 @@ func TestSession_Create(t *testing.T) { opt: []Option{WithExpirationTime(exp)}, }, want: &Session{ - UserId: composedOf.UserId, - HostId: composedOf.HostId, - TargetId: composedOf.TargetId, - HostSetId: composedOf.HostSetId, - AuthTokenId: composedOf.AuthTokenId, - ScopeId: composedOf.ScopeId, - Endpoint: "tcp://127.0.0.1:22", - ExpirationTime: composedOf.ExpirationTime, - ConnectionLimit: composedOf.ConnectionLimit, + UserId: composedOf.UserId, + HostId: composedOf.HostId, + TargetId: composedOf.TargetId, + HostSetId: composedOf.HostSetId, + AuthTokenId: composedOf.AuthTokenId, + ScopeId: composedOf.ScopeId, + Endpoint: "tcp://127.0.0.1:22", + ExpirationTime: composedOf.ExpirationTime, + ConnectionLimit: composedOf.ConnectionLimit, + DynamicCredentials: composedOf.DynamicCredentials, }, create: true, }, diff --git a/internal/session/testing.go b/internal/session/testing.go index b0ab4d5fd..dd90fb873 100644 --- a/internal/session/testing.go +++ b/internal/session/testing.go @@ -94,6 +94,13 @@ func TestSession(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, c Compos require.NoError(err) err = rw.Create(context.Background(), s, opts.withDbOpts...) require.NoError(err) + + for _, cred := range s.DynamicCredentials { + cred.SessionId = s.PublicId + err := rw.Create(context.Background(), cred) + require.NoError(err) + } + ss, err := fetchStates(context.Background(), rw, s.PublicId, append(opts.withDbOpts, db.WithOrder("start_time desc"))...) require.NoError(err) s.States = ss @@ -121,9 +128,7 @@ func TestSessionParams(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, ia org, proj := iam.TestScopes(t, iamRepo) cats := static.TestCatalogs(t, conn, proj.PublicId, 1) - hosts := static.TestHosts(t, conn, cats[0].PublicId, 1) - sets := static.TestSets(t, conn, cats[0].PublicId, 1) _ = static.TestSetMembers(t, conn, sets[0].PublicId, hosts) @@ -132,7 +137,7 @@ func TestSessionParams(t *testing.T, conn *gorm.DB, wrapper wrapping.Wrapper, ia kms := kms.TestKms(t, conn, wrapper) targetRepo, err := target.NewRepository(rw, rw, kms) require.NoError(err) - _, _, err = targetRepo.AddTargetHostSets(ctx, tcpTarget.GetPublicId(), tcpTarget.GetVersion(), []string{sets[0].PublicId}) + _, _, _, err = targetRepo.AddTargetHostSets(ctx, tcpTarget.GetPublicId(), tcpTarget.GetVersion(), []string{sets[0].PublicId}) require.NoError(err) authMethod := password.TestAuthMethods(t, conn, org.PublicId, 1)[0] diff --git a/internal/target/credential_library.go b/internal/target/credential_library.go new file mode 100644 index 000000000..d25eeb77b --- /dev/null +++ b/internal/target/credential_library.go @@ -0,0 +1,87 @@ +package target + +import ( + "fmt" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target/store" + "google.golang.org/protobuf/proto" +) + +// A CredentialLibrary represents the relationship between a target +// and a credential library. +type CredentialLibrary struct { + *store.CredentialLibrary + tableName string `gorm:"-"` +} + +// NewCredentialLibrary creates a new in memory CredentialLibrary +// representing the relationship between targetId and credentialLibraryId. +func NewCredentialLibrary(targetId, credentialLibraryId string, _ ...Option) (*CredentialLibrary, error) { + const op = "target.NewCredentialLibrary" + if targetId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no target id") + } + if credentialLibraryId == "" { + return nil, errors.New(errors.InvalidParameter, op, "no credential library id") + } + + t := &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + TargetId: targetId, + CredentialLibraryId: credentialLibraryId, + CredentialPurpose: string(credential.ApplicationPurpose), // application is the only purpose currently supported + }, + } + return t, nil +} + +func allocCredentialLibrary() *CredentialLibrary { + return &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{}, + } +} + +func (t *CredentialLibrary) clone() *CredentialLibrary { + cp := proto.Clone(t.CredentialLibrary) + return &CredentialLibrary{ + CredentialLibrary: cp.(*store.CredentialLibrary), + } +} + +// TableName returns the table name. +func (t *CredentialLibrary) TableName() string { + if t.tableName != "" { + return t.tableName + } + return "target_credential_library" +} + +// SetTableName sets the table name. +func (t *CredentialLibrary) SetTableName(n string) { + t.tableName = n +} + +func (t *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{fmt.Sprintf("%s:%s:%s", t.TargetId, t.CredentialLibraryId, t.CredentialPurpose)}, + "resource-type": []string{"target-credential-library"}, + "op-type": []string{op.String()}, + } + return metadata +} + +// A TargetLibrary represents the relationship between a target and a +// credential library and includes the id of the credential store that the +// library is a part of and the library's name and description. +type TargetLibrary struct { + *store.CredentialLibrary + StoreId string +} + +// TableName returns the tablename to override the default gorm table name +func (ts *TargetLibrary) TableName() string { + return "target_library" +} diff --git a/internal/target/credential_library_test.go b/internal/target/credential_library_test.go new file mode 100644 index 000000000..38a86a4b9 --- /dev/null +++ b/internal/target/credential_library_test.go @@ -0,0 +1,68 @@ +package target + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/target/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCredentialLibrary_New(t *testing.T) { + type args struct { + targetId string + libraryId string + } + tests := []struct { + name string + args args + want *CredentialLibrary + wantErr errors.Code + }{ + { + name: "no-targetId", + args: args{ + libraryId: "lib_0000000", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "no-libraryId", + args: args{ + targetId: "targ_0000000", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "valid", + args: args{ + targetId: "targ_0000000", + libraryId: "lib_0000000", + }, + want: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + TargetId: "targ_0000000", + CredentialLibraryId: "lib_0000000", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := NewCredentialLibrary(tt.args.targetId, tt.args.libraryId) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/target/immutable_fields_test.go b/internal/target/immutable_fields_test.go index 542dd0888..78f30e126 100644 --- a/internal/target/immutable_fields_test.go +++ b/internal/target/immutable_fields_test.go @@ -169,7 +169,7 @@ func TestTargetHostSet_ImmutableFields(t *testing.T) { updateTarget := TestTcpTarget(t, conn, proj.PublicId, testId(t)) updateHset := hsets[1] - _, gotHostSets, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 1, []string{hsets[0].PublicId}) + _, gotHostSets, _, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 1, []string{hsets[0].PublicId}) require.NoError(t, err) require.Equal(t, 1, len(gotHostSets)) new, err := NewTargetHostSet(projTarget.PublicId, gotHostSets[0].PublicId) diff --git a/internal/target/options.go b/internal/target/options.go index 5bfe23de2..4334ce496 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -26,6 +26,7 @@ type options struct { withUserId string withTargetType *TargetType withHostSets []string + withCredentialLibraries []string withSessionMaxSeconds uint32 withSessionConnectionLimit int32 withPublicId string @@ -44,6 +45,7 @@ func getDefaultOptions() options { withUserId: "", withTargetType: nil, withHostSets: nil, + withCredentialLibraries: nil, withSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), withSessionConnectionLimit: 1, withPublicId: "", @@ -123,6 +125,13 @@ func WithHostSets(hs []string) Option { } } +// WithCredentialLibraries provides an option for providing a list of credential library ids +func WithCredentialLibraries(cl []string) Option { + return func(o *options) { + o.withCredentialLibraries = cl + } +} + func WithSessionMaxSeconds(dur uint32) Option { return func(o *options) { o.withSessionMaxSeconds = dur diff --git a/internal/target/options_test.go b/internal/target/options_test.go index 293e4128b..bdf3e75fe 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -104,4 +104,11 @@ func Test_GetOpts(t *testing.T) { testOpts.withWorkerFilter = `"/foo" == "bar"` assert.Equal(opts, testOpts) }) + t.Run("WithCredentialLibraries", func(t *testing.T) { + assert := assert.New(t) + opts := getOpts(WithCredentialLibraries([]string{"alice", "bob"})) + testOpts := getDefaultOptions() + testOpts.withCredentialLibraries = []string{"alice", "bob"} + assert.Equal(opts, testOpts) + }) } diff --git a/internal/target/query.go b/internal/target/query.go new file mode 100644 index 000000000..afc3b4d81 --- /dev/null +++ b/internal/target/query.go @@ -0,0 +1,46 @@ +package target + +const ( + setChangesQuery = ` +with +set_libraries (library_id) as ( + -- returns the SET list + select public_id + from credential_library + where public_id in (%s) +), +current_libraries (library_id) as ( + -- returns the current list + select credential_library_id + from target_credential_library + where target_id = $1 +), +keep_libraries (library_id) as ( + -- returns the KEEP list + select library_id + from current_libraries + where library_id in (select * from set_libraries) +), +delete_libraries (library_id) as ( + -- returns the DELETE list + select library_id + from current_libraries + where library_id not in (select * from set_libraries) +), +insert_libraries (library_id) as ( + -- returns the ADD list + select library_id + from set_libraries + where library_id not in (select * from keep_libraries) +), +final (action, library_id) as ( + select 'delete', library_id + from delete_libraries + union + select 'add', library_id + from insert_libraries +) +select * from final +order by action, library_id; +` +) diff --git a/internal/target/repository.go b/internal/target/repository.go index 9b49d10a4..26b425794 100644 --- a/internal/target/repository.go +++ b/internal/target/repository.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/boundary/internal/oplog" ) -// Clonable provides a cloning interface +// Cloneable provides a cloning interface type Cloneable interface { Clone() interface{} } @@ -53,14 +53,15 @@ func NewRepository(r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repo } // LookupTarget will look up a target in the repository and return the target -// with its host set ids. If the target is not found, it will return nil, nil, nil. +// with its host set ids and credential library ids. If the target is not found, +// it will return nil, nil, nil, nil. // No options are currently supported. -func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, opt ...Option) (Target, []*TargetSet, error) { +func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, opt ...Option) (Target, []*TargetSet, []*TargetLibrary, error) { const op = "target.(Repository).LookupTarget" opts := getOpts(opt...) if publicIdOrName == "" { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing public id") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing public id") } var where []string @@ -70,33 +71,34 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op scopeNameEmpty := opts.withScopeName == "" if !nameEmpty { if opts.withName != publicIdOrName { - return nil, nil, errors.New(errors.InvalidParameter, op, "name passed in but does not match publicId") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "name passed in but does not match publicId") } where, whereArgs = append(where, "lower(name) = lower(?)"), append(whereArgs, opts.withName) switch { case scopeIdEmpty && scopeNameEmpty: - return nil, nil, errors.New(errors.InvalidParameter, op, "using name but both scope ID and scope name are empty") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "using name but both scope ID and scope name are empty") case !scopeIdEmpty && !scopeNameEmpty: - return nil, nil, errors.New(errors.InvalidParameter, op, "using name but both scope ID and scope name are set") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "using name but both scope ID and scope name are set") case !scopeIdEmpty: where, whereArgs = append(where, "scope_id = ?"), append(whereArgs, opts.withScopeId) case !scopeNameEmpty: where, whereArgs = append(where, "scope_id = (select public_id from iam_scope where lower(name) = lower(?))"), append(whereArgs, opts.withScopeName) default: - return nil, nil, errors.New(errors.InvalidParameter, op, "unknown combination of parameters") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "unknown combination of parameters") } } else { switch { case !scopeIdEmpty: - return nil, nil, errors.New(errors.InvalidParameter, op, "passed in scope ID when using target ID for lookup") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "passed in scope ID when using target ID for lookup") case !scopeNameEmpty: - return nil, nil, errors.New(errors.InvalidParameter, op, "passed in scope name when using target ID for lookup") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "passed in scope name when using target ID for lookup") } } target := allocTargetView() target.PublicId = publicIdOrName var hostSets []*TargetSet + var credLibs []*TargetLibrary _, err := r.writer.DoTx( ctx, db.StdRetryCnt, @@ -117,32 +119,23 @@ func (r *Repository) LookupTarget(ctx context.Context, publicIdOrName string, op if hostSets, err = fetchSets(ctx, read, target.PublicId); err != nil { return errors.Wrap(err, op) } + if credLibs, err = fetchLibraries(ctx, read, target.PublicId); err != nil { + return errors.Wrap(err, op) + } return nil }, ) if err != nil { if errors.IsNotFoundError(err) { - return nil, nil, nil + return nil, nil, nil, nil } - return nil, nil, errors.Wrap(err, op) + return nil, nil, nil, errors.Wrap(err, op) } subtype, err := target.targetSubtype() if err != nil { - return nil, nil, errors.Wrap(err, op) + return nil, nil, nil, errors.Wrap(err, op) } - return subtype, hostSets, nil -} - -func fetchSets(ctx context.Context, r db.Reader, targetId string) ([]*TargetSet, error) { - const op = "target.fetchSets" - var hostSets []*TargetSet - if err := r.SearchWhere(ctx, &hostSets, "target_id = ?", []interface{}{targetId}); err != nil { - return nil, errors.Wrap(err, op) - } - if len(hostSets) == 0 { - return nil, nil - } - return hostSets, nil + return subtype, hostSets, credLibs, nil } // ListTargets in targets in a scope. Supports the WithScopeId, WithLimit, WithTargetType options. @@ -257,17 +250,17 @@ func (r *Repository) DeleteTarget(ctx context.Context, publicId string, _ ...Opt // update a target in the db repository with an oplog entry. // It currently supports no options. -func (r *Repository) update(ctx context.Context, target Target, version uint32, fieldMaskPaths []string, setToNullPaths []string, _ ...Option) (Target, []*TargetSet, int, error) { +func (r *Repository) update(ctx context.Context, target Target, version uint32, fieldMaskPaths []string, setToNullPaths []string, _ ...Option) (Target, []*TargetSet, []*TargetLibrary, int, error) { const op = "target.(Repository).update" if version == 0 { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") } if target == nil { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "nil target") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "nil target") } cloner, ok := target.(Cloneable) if !ok { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "target is not cloneable") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "target is not cloneable") } dbOpts := []db.Option{ db.WithVersion(&version), @@ -277,13 +270,13 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32, t := allocTargetView() t.PublicId = target.GetPublicId() if err := r.reader.LookupByPublicId(ctx, &t); err != nil { - return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("lookup failed for %s", t.PublicId))) + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("lookup failed for %s", t.PublicId))) } scopeId = t.ScopeId } oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) if err != nil { - return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) } metadata := target.oplog(oplog.OpType_OP_TYPE_UPDATE) dbOpts = append(dbOpts, db.WithOplog(oplogWrapper, metadata)) @@ -291,6 +284,7 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32, var rowsUpdated int var returnedTarget interface{} var hostSets []*TargetSet + var credLibs []*TargetLibrary _, err = r.writer.DoTx( ctx, db.StdRetryCnt, @@ -315,337 +309,15 @@ func (r *Repository) update(ctx context.Context, target Target, version uint32, if hostSets, err = fetchSets(ctx, reader, target.GetPublicId()); err != nil { return errors.Wrap(err, op) } - return nil - }, - ) - if err != nil { - return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) - } - return returnedTarget.(Target), hostSets, rowsUpdated, nil -} -// AddTargetHostSets provides the ability to add host sets (hostSetIds) to a -// target (targetId). The target's current db version must match the -// targetVersion or an error will be returned. The target and a list of -// current host set ids will be returned on success. Zero is not a valid value -// for the WithVersion option and will return an error. -func (r *Repository) AddTargetHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) (Target, []*TargetSet, error) { - const op = "target.(Repository).AddTargetHostSets" - if targetId == "" { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing target id") - } - if targetVersion == 0 { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing version") - } - if len(hostSetIds) == 0 { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing host set ids") - } - newHostSets := make([]interface{}, 0, len(hostSetIds)) - for _, id := range hostSetIds { - ths, err := NewTargetHostSet(targetId, id) - if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) - } - newHostSets = append(newHostSets, ths) - } - t := allocTargetView() - t.PublicId = targetId - if err := r.reader.LookupByPublicId(ctx, &t); err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) - } - var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) - default: - return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) - } - oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) - if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) - } - var currentHostSets []*TargetSet - var updatedTarget interface{} - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(reader db.Reader, w db.Writer) error { - msgs := make([]*oplog.Message, 0, 2) - targetTicket, err := w.GetTicket(target) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) - } - updatedTarget = target.(Cloneable).Clone() - var targetOplogMsg oplog.Message - rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) - } - if rowsUpdated != 1 { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) - } - msgs = append(msgs, &targetOplogMsg) - - hostSetsOplogMsgs := make([]*oplog.Message, 0, len(newHostSets)) - if err := w.CreateItems(ctx, newHostSets, db.NewOplogMsgs(&hostSetsOplogMsgs)); err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to add target host sets")) - } - msgs = append(msgs, hostSetsOplogMsgs...) - - if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) - } - currentHostSets, err = fetchSets(ctx, reader, targetId) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current host sets after adds")) + if credLibs, err = fetchLibraries(ctx, reader, target.GetPublicId()); err != nil { + return errors.Wrap(err, op) } return nil }, ) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("error creating sets")) + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(err, op) } - return updatedTarget.(Target), currentHostSets, nil -} - -// DeleteTargeHostSets deletes host sets from a target (targetId). The target's -// current db version must match the targetVersion or an error will be returned. -// Zero is not a valid value for the WithVersion option and will return an -// error. -func (r *Repository) DeleteTargeHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) (int, error) { - const op = "target.(Repository).DeleteTargeHostSets" - if targetId == "" { - return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") - } - if targetVersion == 0 { - return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") - } - if len(hostSetIds) == 0 { - return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing host set ids") - } - deleteTargeHostSets := make([]interface{}, 0, len(hostSetIds)) - for _, id := range hostSetIds { - ths, err := NewTargetHostSet(targetId, id) - if err != nil { - return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) - } - deleteTargeHostSets = append(deleteTargeHostSets, ths) - } - - t := allocTargetView() - t.PublicId = targetId - if err := r.reader.LookupByPublicId(ctx, &t); err != nil { - return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) - } - - var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) - default: - return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) - } - oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) - if err != nil { - return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) - } - - var totalRowsDeleted int - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(reader db.Reader, w db.Writer) error { - msgs := make([]*oplog.Message, 0, 2) - targetTicket, err := w.GetTicket(target) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) - } - updatedTarget := target.(Cloneable).Clone() - var targetOplogMsg oplog.Message - rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) - } - if rowsUpdated != 1 { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) - } - msgs = append(msgs, &targetOplogMsg) - - hostSetsOplogMsgs := make([]*oplog.Message, 0, len(deleteTargeHostSets)) - rowsDeleted, err := w.DeleteItems(ctx, deleteTargeHostSets, db.NewOplogMsgs(&hostSetsOplogMsgs)) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to delete target host sets")) - } - if rowsDeleted != len(deleteTargeHostSets) { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target host sets deleted %d did not match request for %d", rowsDeleted, len(deleteTargeHostSets))) - } - totalRowsDeleted += rowsDeleted - msgs = append(msgs, hostSetsOplogMsgs...) - - if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) - } - return nil - }, - ) - if err != nil { - return db.NoRowsAffected, errors.Wrap(err, op) - } - return totalRowsDeleted, nil -} - -// SetTargetHostSets will set the target's host sets. Set add and/or delete -// target host sets as need to reconcile the existing sets with the sets -// requested. If hostSetIds is empty, the target host sets will be cleared. Zero -// is not a valid value for the WithVersion option and will return an error. -func (r *Repository) SetTargetHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) ([]*TargetSet, int, error) { - const op = "target.(Repository).SetTargetHostSets" - if targetId == "" { - return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") - } - if targetVersion == 0 { - return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") - } - t := allocTargetView() - t.PublicId = targetId - if err := r.reader.LookupByPublicId(ctx, &t); err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) - } - - // NOTE: calculating that to set can safely happen outside of the write - // transaction since we're using targetVersion to ensure that the only - // operate on the same set of data from these queries that calculate the - // set. - - // TODO(mgaffney) 08/2020: Use SQL to calculate changes. - foundThs, err := fetchSets(ctx, r.reader, targetId) - if err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to search for existing target host sets")) - } - found := map[string]*TargetSet{} - for _, s := range foundThs { - found[s.PublicId] = s - } - addHostSets := make([]interface{}, 0, len(hostSetIds)) - for _, id := range hostSetIds { - if _, ok := found[id]; ok { - // found a match, so do nothing (we want to keep it), but remove it - // from found - delete(found, id) - continue - } - hs, err := NewTargetHostSet(targetId, id) - if err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) - } - addHostSets = append(addHostSets, hs) - } - deleteHostSets := make([]interface{}, 0, len(hostSetIds)) - if len(found) > 0 { - for _, s := range found { - hs, err := NewTargetHostSet(targetId, s.PublicId) - if err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(" unable to create in memory target host set")) - } - deleteHostSets = append(deleteHostSets, hs) - } - } - if len(addHostSets) == 0 && len(deleteHostSets) == 0 { - return foundThs, db.NoRowsAffected, nil - } - - var metadata oplog.Metadata - var target interface{} - switch t.Type { - case TcpTargetType.String(): - tcpT := allocTcpTarget() - tcpT.PublicId = t.PublicId - tcpT.Version = targetVersion + 1 - target = &tcpT - metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) - default: - return nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) - } - oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) - if err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) - } - - var totalRowsAffected int - var currentHostSets []*TargetSet - _, err = r.writer.DoTx( - ctx, - db.StdRetryCnt, - db.ExpBackoff{}, - func(reader db.Reader, w db.Writer) error { - msgs := make([]*oplog.Message, 0, 2) - targetTicket, err := w.GetTicket(target) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) - } - updatedTarget := target.(Cloneable).Clone() - var targetOplogMsg oplog.Message - rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) - } - if rowsUpdated != 1 { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("set target host sets: updated target and %d rows updated", rowsUpdated)) - } - msgs = append(msgs, &targetOplogMsg) - - // Write the new ones in - if len(addHostSets) > 0 { - hostSetOplogMsgs := make([]*oplog.Message, 0, len(addHostSets)) - if err := w.CreateItems(ctx, addHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)); err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to add target host sets")) - } - totalRowsAffected += len(addHostSets) - msgs = append(msgs, hostSetOplogMsgs...) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) - } - - // Anything we didn't take out of found needs to be removed - if len(deleteHostSets) > 0 { - hostSetOplogMsgs := make([]*oplog.Message, 0, len(deleteHostSets)) - rowsDeleted, err := w.DeleteItems(ctx, deleteHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to delete target host set")) - } - if rowsDeleted != len(deleteHostSets) { - return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target host sets deleted %d did not match request for %d", rowsDeleted, len(deleteHostSets))) - } - totalRowsAffected += rowsDeleted - msgs = append(msgs, hostSetOplogMsgs...) - metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) - } - if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) - } - - currentHostSets, err = fetchSets(ctx, reader, targetId) - if err != nil { - return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target host sets after set")) - } - return nil - }, - ) - if err != nil { - return nil, db.NoRowsAffected, errors.Wrap(err, op) - } - return currentHostSets, totalRowsAffected, nil + return returnedTarget.(Target), hostSets, credLibs, rowsUpdated, nil } diff --git a/internal/target/repository_credential_library.go b/internal/target/repository_credential_library.go new file mode 100644 index 000000000..3efa40e4d --- /dev/null +++ b/internal/target/repository_credential_library.go @@ -0,0 +1,398 @@ +package target + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" +) + +// AddTargetCredentialLibraries adds the clIds to the targetId in the repository. The target +// and the list of credential libraries attached to the target, after clIds are added, +// will be returned on success. +// The targetVersion must match the current version of the targetId in the repository. +func (r *Repository) AddTargetCredentialLibraries(ctx context.Context, targetId string, targetVersion uint32, clIds []string, _ ...Option) (Target, []*TargetSet, []*TargetLibrary, error) { + const op = "target.(Repository).AddTargetCredentialLibraries" + if targetId == "" { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing version") + } + if len(clIds) == 0 { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing credential library ids") + } + + addCredLibs := make([]interface{}, 0, len(clIds)) + for _, id := range clIds { + cl, err := NewCredentialLibrary(targetId, id) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory credential library")) + } + addCredLibs = append(addCredLibs, cl) + } + + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + default: + return nil, nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var hostSets []*TargetSet + var credLibs []*TargetLibrary + var updatedTarget interface{} + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget = target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated == 0 { + return errors.New(errors.VersionMismatch, op, "invalid target version") + } + if rowsUpdated > 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + credLibsOplogMsgs := make([]*oplog.Message, 0, len(addCredLibs)) + if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs)); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to create target credential libraries")) + } + msgs = append(msgs, credLibsOplogMsgs...) + + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + hostSets, err = fetchSets(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve credential libraries after adding")) + } + credLibs, err = fetchLibraries(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve credential libraries after adding")) + } + return nil + }, + ) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op) + } + return updatedTarget.(Target), hostSets, credLibs, nil +} + +// DeleteTargetCredentialLibraries deletes credential libraries from a target in the repository. +// The target's current db version must match the targetVersion or an error will be returned. +func (r *Repository) DeleteTargetCredentialLibraries(ctx context.Context, targetId string, targetVersion uint32, clIds []string, _ ...Option) (int, error) { + const op = "target.(Repository).DeleteTargetCredentialLibraries" + if targetId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + if len(clIds) == 0 { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing credential library ids") + } + + deleteCredLibs := make([]interface{}, 0, len(clIds)) + for _, id := range clIds { + cl, err := NewCredentialLibrary(targetId, id) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory credential library")) + } + deleteCredLibs = append(deleteCredLibs, cl) + } + + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + default: + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsDeleted int + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget := target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated == 0 { + return errors.New(errors.VersionMismatch, op, "invalid target version") + } + if rowsUpdated > 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + credLibsOplogMsgs := make([]*oplog.Message, 0, len(deleteCredLibs)) + rowsDeleted, err = w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to delete target credential libraries")) + } + if rowsDeleted != len(deleteCredLibs) { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("credential libraries deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + } + msgs = append(msgs, credLibsOplogMsgs...) + + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + return nil + }, + ) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op) + } + return rowsDeleted, nil +} + +// SetTargetCredentialLibraries will set the target's credential libraries. Set will add +// and/or delete credential libraries as need to reconcile the existing credential libraries +// with the request. If clIds is empty, all the credential libraries will be cleared from the target. +func (r *Repository) SetTargetCredentialLibraries(ctx context.Context, targetId string, targetVersion uint32, clIds []string, _ ...Option) ([]*TargetSet, []*TargetLibrary, int, error) { + const op = "target.(Repository).SetTargetCredentialLibraries" + if targetId == "" { + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + + changes, err := r.changes(ctx, targetId, clIds) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) + } + if len(changes) == 0 { + // Nothing needs to be changed, return early + hostSets, err := fetchSets(ctx, r.reader, targetId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) + } + credLibs, err := fetchLibraries(ctx, r.reader, targetId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) + } + return hostSets, credLibs, db.NoRowsAffected, nil + } + + var deleteCredLibs, addCredLibs []interface{} + for _, c := range changes { + cl, err := NewCredentialLibrary(targetId, c.LibraryId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target credential library")) + } + switch c.Action { + case "delete": + deleteCredLibs = append(deleteCredLibs, cl) + case "add": + addCredLibs = append(addCredLibs, cl) + } + } + + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + default: + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsAffected int + var hostSets []*TargetSet + var credLibs []*TargetLibrary + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget := target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated == 0 { + return errors.New(errors.VersionMismatch, op, "invalid target version") + } + if rowsUpdated > 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + // add new credential libraries + if len(addCredLibs) > 0 { + addMsgs := make([]*oplog.Message, 0, len(addCredLibs)) + if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&addMsgs)); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to add target credential libraries")) + } + rowsAffected += len(addMsgs) + msgs = append(msgs, addMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + } + + // delete existing credential libraries not part of set + if len(deleteCredLibs) > 0 { + delMsgs := make([]*oplog.Message, 0, len(deleteCredLibs)) + rowsDeleted, err := w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&delMsgs)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to delete target credential libraries")) + } + if rowsDeleted != len(delMsgs) { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target credential libraries deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + } + rowsAffected += rowsDeleted + msgs = append(msgs, delMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + + hostSets, err = fetchSets(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target host sets after add/delete")) + } + credLibs, err = fetchLibraries(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target credential libraries after add/delete")) + } + return nil + }, + ) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) + } + return hostSets, credLibs, rowsAffected, nil +} + +type change struct { + Action string + LibraryId string +} + +func (r *Repository) changes(ctx context.Context, targetId string, clIds []string) ([]*change, error) { + const op = "target.(Repository).changes" + var inClauseSpots []string + // starts at 2 because there is already a $1 in the query + for i := 2; i < len(clIds)+2; i++ { + inClauseSpots = append(inClauseSpots, fmt.Sprintf("$%d", i)) + } + inClause := strings.Join(inClauseSpots, ",") + if inClause == "" { + inClause = "''" + } + query := fmt.Sprintf(setChangesQuery, inClause) + + var params []interface{} + params = append(params, targetId) + for _, id := range clIds { + params = append(params, id) + } + rows, err := r.reader.Query(ctx, query, params) + if err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("query failed")) + } + defer rows.Close() + + var changes []*change + for rows.Next() { + var chg change + if err := r.reader.ScanRows(rows, &chg); err != nil { + return nil, errors.Wrap(err, op, errors.WithMsg("scan row failed")) + } + changes = append(changes, &chg) + } + return changes, nil +} + +func fetchLibraries(ctx context.Context, r db.Reader, targetId string) ([]*TargetLibrary, error) { + const op = "target.fetchLibraries" + var libraries []*TargetLibrary + if err := r.SearchWhere(ctx, &libraries, "target_id = ?", []interface{}{targetId}); err != nil { + return nil, errors.Wrap(err, op) + } + if len(libraries) == 0 { + return nil, nil + } + return libraries, nil +} diff --git a/internal/target/repository_credential_library_test.go b/internal/target/repository_credential_library_test.go new file mode 100644 index 000000000..ac13c6859 --- /dev/null +++ b/internal/target/repository_credential_library_test.go @@ -0,0 +1,541 @@ +package target + +import ( + "context" + "sort" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestRepository_AddTargetCredentialLibraries(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, staticProj := iam.TestScopes(t, iamRepo) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + cs := vault.TestCredentialStores(t, conn, wrapper, staticProj.GetPublicId(), 1)[0] + libs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 3) + require.Len(t, libs, 3) + lib1 := libs[0] + lib2 := libs[1] + lib3 := libs[2] + + type args struct { + targetVersion uint32 + credLibIds []string + } + tests := []struct { + name string + args args + wantCredLibIds []string + wantErr bool + wantErrCode errors.Code + }{ + { + name: "zero-version", + args: args{ + targetVersion: 0, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "no-cred-libs", + args: args{ + targetVersion: 1, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid-single-lib", + args: args{ + targetVersion: 1, + credLibIds: []string{lib1.PublicId}, + }, + wantCredLibIds: []string{lib1.PublicId}, + wantErr: false, + }, + { + name: "valid-multiple-libs", + args: args{ + targetVersion: 1, + credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}, + }, + wantCredLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}, + wantErr: false, + }, + { + name: "invalid-lib-id", + args: args{ + targetVersion: 1, + credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId, "invalid-lib-id"}, + }, + wantErr: true, + wantErrCode: errors.NotSpecificIntegrity, + }, + { + name: "bad-version", + args: args{ + targetVersion: 1000, + credLibIds: []string{lib1.PublicId}, + }, + wantErr: true, + wantErrCode: errors.VersionMismatch, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + projTarget := TestTcpTarget(t, conn, staticProj.PublicId, tt.name) + gotTarget, _, gotCredLibs, err := repo.AddTargetCredentialLibraries(context.Background(), projTarget.PublicId, tt.args.targetVersion, tt.args.credLibIds) + if tt.wantErr { + require.Error(err) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) + return + } + require.NoError(err) + assert.Len(gotCredLibs, len(tt.wantCredLibIds)) + gotCredLibsMap := map[string]*TargetLibrary{} + for _, s := range gotCredLibs { + gotCredLibsMap[s.CredentialLibraryId] = s + } + for _, id := range tt.wantCredLibIds { + assert.NotEmpty(gotCredLibsMap[id]) + } + + // test to see of the target version update oplog was created + err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + + foundCredLibs, err := fetchLibraries(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + assert.Len(foundCredLibs, len(gotCredLibsMap)) + for _, s := range foundCredLibs { + assert.NotEmpty(gotCredLibsMap[s.CredentialLibraryId]) + assert.Equal(projTarget.PublicId, s.TargetId) + } + + target, _, lookupCredLibs, err := repo.LookupTarget(context.Background(), projTarget.PublicId) + require.NoError(err) + assert.Equal(tt.args.targetVersion+1, target.GetVersion()) + assert.Equal(projTarget.GetVersion(), target.GetVersion()-1) + assert.True(proto.Equal(gotTarget.(*TcpTarget), target.(*TcpTarget))) + assert.Equal(gotCredLibs, lookupCredLibs) + }) + } + t.Run("add-existing", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "add-existing") + _, _, gotCredLibs, err := repo.AddTargetCredentialLibraries(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId}) + require.NoError(err) + assert.Len(gotCredLibs, 1) + assert.Equal(lib1.PublicId, gotCredLibs[0].CredentialLibraryId) + + // Adding lib1 again should error + _, _, _, err = repo.AddTargetCredentialLibraries(context.Background(), projTarget.PublicId, 2, []string{lib1.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.NotUnique), err)) + + // Adding multiple with lib1 in set should error + _, _, _, err = repo.AddTargetCredentialLibraries(context.Background(), projTarget.PublicId, 2, []string{lib3.PublicId, lib2.PublicId, lib1.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.NotUnique), err)) + + // Previous transactions should have been rolled back and only lib1 should be associated + gotCredLibs, err = fetchLibraries(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + assert.Len(gotCredLibs, 1) + assert.Equal(lib1.PublicId, gotCredLibs[0].CredentialLibraryId) + }) + t.Run("target-not-found", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, _, _, err := repo.AddTargetCredentialLibraries(context.Background(), "fake-target-id", 1, []string{lib1.PublicId}) + + require.Error(err) + assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) + }) +} + +func TestRepository_DeleteTargetCredentialLibraries(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + type args struct { + targetIdOverride *string + targetVersionOverride *uint32 + createCnt int + deleteCnt int + } + tests := []struct { + name string + args args + wantRowsDeleted int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "valid", + args: args{ + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 5, + wantErr: false, + }, + { + name: "valid-keeping-some", + args: args{ + createCnt: 5, + deleteCnt: 2, + }, + wantRowsDeleted: 2, + wantErr: false, + }, + { + name: "no-deletes", + args: args{ + createCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "not-found", + args: args{ + targetIdOverride: func() *string { id := testId(t); return &id }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrCode: errors.RecordNotFound, + }, + { + name: "missing-target-id", + args: args{ + targetIdOverride: func() *string { id := ""; return &id }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "zero-version", + args: args{ + targetVersionOverride: func() *uint32 { v := uint32(0); return &v }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "bad-version", + args: args{ + targetVersionOverride: func() *uint32 { v := uint32(1000); return &v }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantErrCode: errors.VersionMismatch, + }, + } + css := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) + for i, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + cs := css[i] + + target := TestTcpTarget(t, conn, proj.PublicId, tt.name) + + clIds := make([]string, 0, tt.args.createCnt) + if tt.args.createCnt > 0 { + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.PublicId, tt.args.createCnt) + for _, cl := range credLibs { + clIds = append(clIds, cl.PublicId) + } + } + _, _, addedCredLibs, err := repo.AddTargetCredentialLibraries(context.Background(), target.GetPublicId(), 1, clIds) + require.NoError(err) + assert.Equal(tt.args.createCnt, len(addedCredLibs)) + + deleteCredLibs := make([]string, 0, tt.args.deleteCnt) + for i := 0; i < tt.args.deleteCnt; i++ { + deleteCredLibs = append(deleteCredLibs, clIds[i]) + } + var targetId string + switch { + case tt.args.targetIdOverride != nil: + targetId = *tt.args.targetIdOverride + default: + targetId = target.GetPublicId() + } + var targetVersion uint32 + switch { + case tt.args.targetVersionOverride != nil: + targetVersion = *tt.args.targetVersionOverride + default: + targetVersion = 2 + } + deletedRows, err := repo.DeleteTargetCredentialLibraries(context.Background(), targetId, targetVersion, deleteCredLibs) + if tt.wantErr { + require.Error(err) + assert.Equal(0, deletedRows) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) + return + } + require.NoError(err) + assert.Equal(tt.wantRowsDeleted, deletedRows) + + // we should find the oplog for the delete of target credential libraries + err = db.TestVerifyOplog(t, rw, target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + }) + } + t.Run("delete-unassociated", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, proj := iam.TestScopes(t, iamRepo) + cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + libs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 3) + require.Len(libs, 3) + lib1 := libs[0] + lib2 := libs[1] + lib3 := libs[2] + + projTarget := TestTcpTarget(t, conn, proj.PublicId, "add-existing") + _, _, gotCredLibs, err := repo.AddTargetCredentialLibraries(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId, lib2.PublicId}) + require.NoError(err) + assert.Len(gotCredLibs, 2) + + // Deleting an unassociated library should return an error + delCount, err := repo.DeleteTargetCredentialLibraries(context.Background(), projTarget.PublicId, 2, []string{lib3.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) + assert.Equal(0, delCount) + + // Deleting libraries which includes an unassociated libary should return an error + delCount, err = repo.DeleteTargetCredentialLibraries(context.Background(), projTarget.PublicId, 2, []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) + assert.Equal(0, delCount) + + // Previous transactions should have been rolled back and only lib1 should be associated + gotCredLibs, err = fetchLibraries(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + assert.Len(gotCredLibs, 2) + }) +} + +func TestRepository_SetTargetCredentialLibraries(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + + cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) + lib1 := credLibs[0] + lib2 := credLibs[1] + + setupFn := func(target Target) []*TargetLibrary { + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 10) + clIds := make([]string, 0, len(credLibs)) + for _, cl := range credLibs { + clIds = append(clIds, cl.PublicId) + } + + _, _, created, err := repo.AddTargetCredentialLibraries(context.Background(), target.GetPublicId(), 1, clIds) + require.NoError(t, err) + require.Equal(t, 10, len(created)) + return created + } + type args struct { + targetVersion uint32 + clIds []string + addToOrigLibs bool + } + tests := []struct { + name string + setup func(Target) []*TargetLibrary + args args + wantAffectedRows int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "clear", + setup: setupFn, + args: args{ + targetVersion: 2, + clIds: []string{}, + }, + wantErr: false, + wantAffectedRows: 10, + }, + { + name: "no-change", + setup: setupFn, + args: args{ + targetVersion: 2, + clIds: []string{}, + addToOrigLibs: true, + }, + wantErr: false, + wantAffectedRows: 0, + }, + { + name: "add-cred-libs", + setup: setupFn, + args: args{ + targetVersion: 2, + clIds: []string{lib1.PublicId, lib2.PublicId}, + addToOrigLibs: true, + }, + wantErr: false, + wantAffectedRows: 2, + }, + { + name: "zero version", + setup: setupFn, + args: args{ + targetVersion: 0, + clIds: []string{lib1.PublicId, lib2.PublicId}, + addToOrigLibs: true, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "bad version", + setup: setupFn, + args: args{ + targetVersion: 1000, + clIds: []string{lib1.PublicId, lib2.PublicId}, + addToOrigLibs: true, + }, + wantErr: true, + wantErrCode: errors.VersionMismatch, + }, + { + name: "remove existing and add cred libs", + setup: setupFn, + args: args{ + targetVersion: 2, + clIds: []string{lib1.PublicId, lib2.PublicId}, + addToOrigLibs: false, + }, + wantErr: false, + wantAffectedRows: 12, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + target := TestTcpTarget(t, conn, proj.PublicId, tt.name) + + var origCredLibs []*TargetLibrary + if tt.setup != nil { + origCredLibs = tt.setup(target) + } + if tt.args.addToOrigLibs { + origIds := make([]string, 0, len(origCredLibs)) + for _, cl := range origCredLibs { + origIds = append(origIds, cl.CredentialLibraryId) + } + tt.args.clIds = append(tt.args.clIds, origIds...) + } + + origTarget, _, lookupCredLibs, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + require.NoError(err) + assert.Equal(origCredLibs, lookupCredLibs) + + _, got, affectedRows, err := repo.SetTargetCredentialLibraries(context.Background(), target.GetPublicId(), tt.args.targetVersion, tt.args.clIds) + if tt.wantErr { + require.Error(err) + assert.Equal(0, affectedRows) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) + return + } + t.Log(err) + require.NoError(err) + assert.Equal(tt.wantAffectedRows, affectedRows) + assert.Equal(len(tt.args.clIds), len(got)) + + var wantIds []string + wantIds = append(wantIds, tt.args.clIds...) + sort.Strings(wantIds) + + var gotIds []string + if len(got) > 0 { + gotIds = make([]string, 0, len(got)) + for _, s := range got { + gotIds = append(gotIds, s.CredentialLibraryId) + } + } + sort.Strings(gotIds) + assert.Equal(wantIds, gotIds) + + foundTarget, _, _, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + require.NoError(err) + if tt.name != "no-change" { + assert.Equalf(tt.args.targetVersion+1, foundTarget.GetVersion(), "%s unexpected version: %d/%d", tt.name, tt.args.targetVersion+1, foundTarget.GetVersion()) + assert.Equalf(origTarget.GetVersion(), foundTarget.GetVersion()-1, "%s unexpected version: %d/%d", tt.name, origTarget.GetVersion(), foundTarget.GetVersion()-1) + } + }) + } + t.Run("missing-target-id", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, _, _, err := repo.SetTargetCredentialLibraries(context.Background(), "", 1, []string{lib1.PublicId}) + + require.Error(err) + assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error()) + }) + t.Run("target-not-found", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + _, _, _, err := repo.SetTargetCredentialLibraries(context.Background(), "fake-target-id", 1, []string{lib1.PublicId}) + + require.Error(err) + assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) + }) +} diff --git a/internal/target/repository_host_set.go b/internal/target/repository_host_set.go new file mode 100644 index 000000000..2d29acc3d --- /dev/null +++ b/internal/target/repository_host_set.go @@ -0,0 +1,360 @@ +package target + +import ( + "context" + "fmt" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" +) + +// AddTargetHostSets provides the ability to add host sets (hostSetIds) to a +// target (targetId). The target's current db version must match the +// targetVersion or an error will be returned. The target and a list of +// current host set ids will be returned on success. Zero is not a valid value +// for the WithVersion option and will return an error. +func (r *Repository) AddTargetHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) (Target, []*TargetSet, []*TargetLibrary, error) { + const op = "target.(Repository).AddTargetHostSets" + if targetId == "" { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing version") + } + if len(hostSetIds) == 0 { + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing host set ids") + } + newHostSets := make([]interface{}, 0, len(hostSetIds)) + for _, id := range hostSetIds { + ths, err := NewTargetHostSet(targetId, id) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) + } + newHostSets = append(newHostSets, ths) + } + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + default: + return nil, nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + var currentHostSets []*TargetSet + var currentCredLibs []*TargetLibrary + var updatedTarget interface{} + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget = target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated != 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + hostSetsOplogMsgs := make([]*oplog.Message, 0, len(newHostSets)) + if err := w.CreateItems(ctx, newHostSets, db.NewOplogMsgs(&hostSetsOplogMsgs)); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to add target host sets")) + } + msgs = append(msgs, hostSetsOplogMsgs...) + + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + currentHostSets, err = fetchSets(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current host sets after adds")) + } + currentCredLibs, err = fetchLibraries(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current credential libraries after adds")) + } + return nil + }, + ) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("error creating sets")) + } + return updatedTarget.(Target), currentHostSets, currentCredLibs, nil +} + +// DeleteTargeHostSets deletes host sets from a target (targetId). The target's +// current db version must match the targetVersion or an error will be returned. +// Zero is not a valid value for the WithVersion option and will return an +// error. +func (r *Repository) DeleteTargeHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) (int, error) { + const op = "target.(Repository).DeleteTargeHostSets" + if targetId == "" { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + if len(hostSetIds) == 0 { + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing host set ids") + } + deleteTargeHostSets := make([]interface{}, 0, len(hostSetIds)) + for _, id := range hostSetIds { + ths, err := NewTargetHostSet(targetId, id) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) + } + deleteTargeHostSets = append(deleteTargeHostSets, ths) + } + + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + default: + return db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var totalRowsDeleted int + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget := target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated != 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + hostSetsOplogMsgs := make([]*oplog.Message, 0, len(deleteTargeHostSets)) + rowsDeleted, err := w.DeleteItems(ctx, deleteTargeHostSets, db.NewOplogMsgs(&hostSetsOplogMsgs)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to delete target host sets")) + } + if rowsDeleted != len(deleteTargeHostSets) { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target host sets deleted %d did not match request for %d", rowsDeleted, len(deleteTargeHostSets))) + } + totalRowsDeleted += rowsDeleted + msgs = append(msgs, hostSetsOplogMsgs...) + + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + return nil + }, + ) + if err != nil { + return db.NoRowsAffected, errors.Wrap(err, op) + } + return totalRowsDeleted, nil +} + +// SetTargetHostSets will set the target's host sets. Set add and/or delete +// target host sets as need to reconcile the existing sets with the sets +// requested. If hostSetIds is empty, the target host sets will be cleared. Zero +// is not a valid value for the WithVersion option and will return an error. +func (r *Repository) SetTargetHostSets(ctx context.Context, targetId string, targetVersion uint32, hostSetIds []string, _ ...Option) ([]*TargetSet, []*TargetLibrary, int, error) { + const op = "target.(Repository).SetTargetHostSets" + if targetId == "" { + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target id") + } + if targetVersion == 0 { + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing version") + } + t := allocTargetView() + t.PublicId = targetId + if err := r.reader.LookupByPublicId(ctx, &t); err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", targetId))) + } + + // NOTE: calculating that to set can safely happen outside of the write + // transaction since we're using targetVersion to ensure that the only + // operate on the same set of data from these queries that calculate the + // set. + + // TODO(mgaffney) 08/2020: Use SQL to calculate changes. + foundThs, err := fetchSets(ctx, r.reader, targetId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to search for existing target host sets")) + } + found := map[string]*TargetSet{} + for _, s := range foundThs { + found[s.PublicId] = s + } + addHostSets := make([]interface{}, 0, len(hostSetIds)) + for _, id := range hostSetIds { + if _, ok := found[id]; ok { + // found a match, so do nothing (we want to keep it), but remove it + // from found + delete(found, id) + continue + } + hs, err := NewTargetHostSet(targetId, id) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) + } + addHostSets = append(addHostSets, hs) + } + deleteHostSets := make([]interface{}, 0, len(hostSetIds)) + if len(found) > 0 { + for _, s := range found { + hs, err := NewTargetHostSet(targetId, s.PublicId) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(" unable to create in memory target host set")) + } + deleteHostSets = append(deleteHostSets, hs) + } + } + if len(addHostSets) == 0 && len(deleteHostSets) == 0 { + return foundThs, nil, db.NoRowsAffected, nil + } + + var metadata oplog.Metadata + var target interface{} + switch t.Type { + case TcpTargetType.String(): + tcpT := allocTcpTarget() + tcpT.PublicId = t.PublicId + tcpT.Version = targetVersion + 1 + target = &tcpT + metadata = tcpT.oplog(oplog.OpType_OP_TYPE_UPDATE) + default: + return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) + } + oplogWrapper, err := r.kms.GetWrapper(ctx, t.GetScopeId(), kms.KeyPurposeOplog) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var totalRowsAffected int + var currentHostSets []*TargetSet + var currentCredLibs []*TargetLibrary + _, err = r.writer.DoTx( + ctx, + db.StdRetryCnt, + db.ExpBackoff{}, + func(reader db.Reader, w db.Writer) error { + msgs := make([]*oplog.Message, 0, 2) + targetTicket, err := w.GetTicket(target) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to get ticket")) + } + updatedTarget := target.(Cloneable).Clone() + var targetOplogMsg oplog.Message + rowsUpdated, err := w.Update(ctx, updatedTarget, []string{"Version"}, nil, db.NewOplogMsg(&targetOplogMsg), db.WithVersion(&targetVersion)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to update target version")) + } + if rowsUpdated != 1 { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("set target host sets: updated target and %d rows updated", rowsUpdated)) + } + msgs = append(msgs, &targetOplogMsg) + + // Write the new ones in + if len(addHostSets) > 0 { + hostSetOplogMsgs := make([]*oplog.Message, 0, len(addHostSets)) + if err := w.CreateItems(ctx, addHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to add target host sets")) + } + totalRowsAffected += len(addHostSets) + msgs = append(msgs, hostSetOplogMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + } + + // Anything we didn't take out of found needs to be removed + if len(deleteHostSets) > 0 { + hostSetOplogMsgs := make([]*oplog.Message, 0, len(deleteHostSets)) + rowsDeleted, err := w.DeleteItems(ctx, deleteHostSets, db.NewOplogMsgs(&hostSetOplogMsgs)) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to delete target host set")) + } + if rowsDeleted != len(deleteHostSets) { + return errors.New(errors.MultipleRecords, op, fmt.Sprintf("target host sets deleted %d did not match request for %d", rowsDeleted, len(deleteHostSets))) + } + totalRowsAffected += rowsDeleted + msgs = append(msgs, hostSetOplogMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) + } + + currentHostSets, err = fetchSets(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target host sets after set")) + } + currentCredLibs, err = fetchLibraries(ctx, reader, targetId) + if err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to retrieve current target credential libraries after set")) + } + + return nil + }, + ) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(err, op) + } + return currentHostSets, currentCredLibs, totalRowsAffected, nil +} + +func fetchSets(ctx context.Context, r db.Reader, targetId string) ([]*TargetSet, error) { + const op = "target.fetchSets" + var hostSets []*TargetSet + if err := r.SearchWhere(ctx, &hostSets, "target_id = ?", []interface{}{targetId}); err != nil { + return nil, errors.Wrap(err, op) + } + if len(hostSets) == 0 { + return nil, nil + } + return hostSets, nil +} diff --git a/internal/target/repository_host_set_test.go b/internal/target/repository_host_set_test.go new file mode 100644 index 000000000..a31e5cf8f --- /dev/null +++ b/internal/target/repository_host_set_test.go @@ -0,0 +1,556 @@ +package target + +import ( + "context" + "sort" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/host/static" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +func TestRepository_AddTargetHostSets(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + staticOrg, staticProj := iam.TestScopes(t, iamRepo) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + createHostSetsFn := func(orgs, projects []string) []string { + results := []string{} + for _, publicId := range orgs { + cats := static.TestCatalogs(t, conn, publicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) + results = append(results, hsets[0].PublicId) + } + for _, publicId := range projects { + cats := static.TestCatalogs(t, conn, publicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) + results = append(results, hsets[0].PublicId) + } + return results + } + + type args struct { + targetVersion uint32 + wantTargetIds bool + opt []Option + } + tests := []struct { + name string + args args + wantErr bool + wantErrIs error + }{ + { + name: "valid", + args: args{ + targetVersion: 1, + wantTargetIds: true, + }, + wantErr: false, + }, + { + name: "bad-version", + args: args{ + targetVersion: 1000, + wantTargetIds: true, + }, + wantErr: true, + }, + { + name: "zero-version", + args: args{ + targetVersion: 0, + wantTargetIds: true, + }, + wantErr: true, + }, + { + name: "no-host-sets", + args: args{ + targetVersion: 1, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + require.NoError(conn.Where("1=1").Delete(allocTargetHostSet()).Error) + require.NoError(conn.Where("1=1").Delete(allocTcpTarget()).Error) + + projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "static-proj") + + var hostSetIds []string + origTarget, origHostSet, _, err := repo.LookupTarget(context.Background(), projTarget.PublicId) + require.NoError(err) + require.Equal(0, len(origHostSet)) + + if tt.args.wantTargetIds { + hostSetIds = createHostSetsFn([]string{staticOrg.PublicId}, []string{staticProj.PublicId}) + } + + gotTarget, gotHostSets, _, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, tt.args.targetVersion, hostSetIds, tt.args.opt...) + if tt.wantErr { + require.Error(err) + if tt.wantErrIs != nil { + assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) + } + // test to see of the target version update oplog was not created + err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + + // TODO (jimlambrt 9/2020) - unfortunately, we can currently + // test to make sure that the oplog entry for a target create + // doesn't exist because the db.TestVerifyOplog doesn't really + // support that level of testing and the previous call to + // TestTcpTarget would create an oplog entry for the + // create on the target. Once TestVerifyOplog supports the + // appropriate granularity, we should add an appropriate assert. + + return + } + require.NoError(err) + gotHostSet := map[string]*TargetSet{} + for _, s := range gotHostSets { + gotHostSet[s.PublicId] = s + } + + // TODO (jimlambrt 9/2020) - unfortunately, we can currently + // test to make sure that the oplog entry for a target create + // doesn't exist because the db.TestVerifyOplog doesn't really + // support that level of testing and the previous call to + // TestTcpTarget would create an oplog entry for the + // create on the target. Once TestVerifyOplog supports the + // appropriate granularity, we should add an appropriate assert. + + // test to see of the target version update oplog was created + err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + + foundHostSets, err := fetchSets(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + for _, s := range foundHostSets { + assert.NotEmpty(gotHostSet[s.PublicId]) + } + + target, ths, _, err := repo.LookupTarget(context.Background(), projTarget.PublicId) + require.NoError(err) + assert.Equal(tt.args.targetVersion+1, target.GetVersion()) + assert.Equal(origTarget.GetVersion(), target.GetVersion()-1) + assert.Equal(gotHostSets, ths) + assert.True(proto.Equal(gotTarget.(*TcpTarget), target.(*TcpTarget))) + }) + } + t.Run("add-existing", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cats := static.TestCatalogs(t, conn, staticProj.PublicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 3) + hs1 := hsets[0] + hs2 := hsets[1] + hs3 := hsets[2] + + projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "add-existing") + _, gotHostSets, _, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 1, []string{hs1.PublicId}) + require.NoError(err) + assert.Len(gotHostSets, 1) + assert.Equal(hs1.PublicId, gotHostSets[0].PublicId) + + // Adding hs1 again should error + _, _, _, err = repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 2, []string{hs1.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.NotUnique), err)) + + // Adding multiple with hs1 in set should error + _, _, _, err = repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 2, []string{hs3.PublicId, hs2.PublicId, hs1.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.NotUnique), err)) + + // Previous transactions should have been rolled back and only hs1 should be associated + gotHostSets, err = fetchSets(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + assert.Len(gotHostSets, 1) + assert.Equal(hs1.PublicId, gotHostSets[0].PublicId) + }) +} + +func TestRepository_DeleteTargetHosts(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + type args struct { + target Target + targetIdOverride *string + targetVersionOverride *uint32 + createCnt int + deleteCnt int + opt []Option + } + tests := []struct { + name string + args args + wantRowsDeleted int + wantErr bool + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "valid"), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 5, + wantErr: false, + }, + { + name: "valid-keeping-some", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "valid-keeping-some"), + createCnt: 5, + deleteCnt: 2, + }, + wantRowsDeleted: 2, + wantErr: false, + }, + { + name: "no-deletes", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "no-deletes"), + createCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "not-found", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "not-found"), + targetIdOverride: func() *string { id := testId(t); return &id }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + }, + { + name: "missing-target-id", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "missing-target-id"), + targetIdOverride: func() *string { id := ""; return &id }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "zero-version", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "zero-version"), + targetVersionOverride: func() *uint32 { v := uint32(0); return &v }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + wantIsErr: errors.InvalidParameter, + }, + { + name: "bad-version", + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "bad-version"), + targetVersionOverride: func() *uint32 { v := uint32(1000); return &v }(), + createCnt: 5, + deleteCnt: 5, + }, + wantRowsDeleted: 0, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + hsIds := make([]string, 0, tt.args.createCnt) + if tt.args.createCnt > 0 { + for i := 0; i < tt.args.createCnt; i++ { + cats := static.TestCatalogs(t, conn, proj.PublicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) + hsIds = append(hsIds, hsets[0].PublicId) + } + } + _, addedHostSets, _, err := repo.AddTargetHostSets(context.Background(), tt.args.target.GetPublicId(), 1, hsIds, tt.args.opt...) + require.NoError(err) + assert.Equal(tt.args.createCnt, len(addedHostSets)) + + deleteHostSets := make([]string, 0, tt.args.deleteCnt) + for i := 0; i < tt.args.deleteCnt; i++ { + deleteHostSets = append(deleteHostSets, hsIds[i]) + } + var targetId string + switch { + case tt.args.targetIdOverride != nil: + targetId = *tt.args.targetIdOverride + default: + targetId = tt.args.target.GetPublicId() + } + var targetVersion uint32 + switch { + case tt.args.targetVersionOverride != nil: + targetVersion = *tt.args.targetVersionOverride + default: + targetVersion = 2 + } + deletedRows, err := repo.DeleteTargeHostSets(context.Background(), targetId, targetVersion, deleteHostSets, tt.args.opt...) + if tt.wantErr { + assert.Error(err) + assert.Equal(0, deletedRows) + assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "unexpected error %s", err.Error()) + // TODO (jimlambrt 9/2020) - unfortunately, we can currently + // test to make sure that the oplog entry for a target update + // doesn't exist because the db.TestVerifyOplog doesn't really + // support that level of testing and the previous call to + // repo.AddTargetHostSets() would create an oplog entry for the + // update to the target. Once TestVerifyOplog supports the + // appropriate granularity, we should add an appropriate assert. + + err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + assert.Error(err) + assert.True(errors.IsNotFoundError(err)) + return + } + require.NoError(err) + assert.Equal(tt.wantRowsDeleted, deletedRows) + + // TODO (jimlambrt 9/2020) - unfortunately, we can currently + // test to make sure that the oplog entry for a target update + // doesn't exist because the db.TestVerifyOplog doesn't really + // support that level of testing and the previous call to + // repo.AddTargetHostSets() would create an oplog entry for the + // update to the target. Once TestVerifyOplog supports the + // appropriate granularity,, we should add an appropriate assert. + + // we should find the oplog for the delete of target host sets + err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + assert.NoError(err) + }) + } + t.Run("delete-unassociated", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + cats := static.TestCatalogs(t, conn, proj.PublicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 3) + hs1 := hsets[0] + hs2 := hsets[1] + hs3 := hsets[2] + + projTarget := TestTcpTarget(t, conn, proj.PublicId, "delete-unassociated") + _, gotHostSets, _, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, 1, []string{hs1.PublicId, hs2.PublicId}) + require.NoError(err) + assert.Len(gotHostSets, 2) + assert.Equal(hs1.PublicId, gotHostSets[0].PublicId) + + // Deleting an unassociated host set should return an error + delCount, err := repo.DeleteTargeHostSets(context.Background(), projTarget.PublicId, 2, []string{hs3.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) + assert.Equal(0, delCount) + + // Deleting host sets which includes an unassociated host set should return an error + delCount, err = repo.DeleteTargeHostSets(context.Background(), projTarget.PublicId, 2, []string{hs1.PublicId, hs2.PublicId, hs3.PublicId}) + require.Error(err) + assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) + assert.Equal(0, delCount) + + // Previous transactions should have been rolled back + gotHostSets, err = fetchSets(context.Background(), rw, projTarget.PublicId) + require.NoError(err) + assert.Len(gotHostSets, 2) + }) +} + +func TestRepository_SetTargetHostSets(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(rw, rw, testKms) + require.NoError(t, err) + + iamRepo := iam.TestRepo(t, conn, wrapper) + _, proj := iam.TestScopes(t, iamRepo) + + testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) + hsets := static.TestSets(t, conn, testCats[0].GetPublicId(), 5) + testHostSetIds := make([]string, 0, len(hsets)) + for _, hs := range hsets { + testHostSetIds = append(testHostSetIds, hs.PublicId) + } + + createHostSetsFn := func() []string { + results := []string{} + for i := 0; i < 10; i++ { + cats := static.TestCatalogs(t, conn, proj.PublicId, 1) + hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) + results = append(results, hsets[0].PublicId) + } + return results + } + + setupFn := func(target Target) []*TargetSet { + hs := createHostSetsFn() + _, created, _, err := repo.AddTargetHostSets(context.Background(), target.GetPublicId(), 1, hs) + require.NoError(t, err) + require.Equal(t, 10, len(created)) + return created + } + type args struct { + target Target + targetVersion uint32 + hostSetIds []string + addToOrigHostSets bool + opt []Option + } + tests := []struct { + name string + setup func(Target) []*TargetSet + args args + wantAffectedRows int + wantErr bool + }{ + { + name: "clear", + setup: setupFn, + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "clear"), + targetVersion: 2, // yep, since setupFn will increment it to 2 + hostSetIds: []string{}, + }, + wantErr: false, + wantAffectedRows: 10, + }, + { + name: "no-change", + setup: setupFn, + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "no-change"), + targetVersion: 2, // yep, since setupFn will increment it to 2 + hostSetIds: []string{}, + addToOrigHostSets: true, + }, + wantErr: false, + wantAffectedRows: 0, + }, + { + name: "add-sets", + setup: setupFn, + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "add-sets"), + targetVersion: 2, // yep, since setupFn will increment it to 2 + hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, + addToOrigHostSets: true, + }, + wantErr: false, + wantAffectedRows: 2, + }, + { + name: "add host sets with zero version", + setup: setupFn, + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "add host sets with zero version"), + targetVersion: 0, + hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, + addToOrigHostSets: true, + }, + wantErr: true, + }, + { + name: "remove existing and add users and grps", + setup: setupFn, + args: args{ + target: TestTcpTarget(t, conn, proj.PublicId, "remove existing and add host sets"), + targetVersion: 2, // yep, since setupFn will increment it to 2 + hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, + addToOrigHostSets: false, + }, + wantErr: false, + wantAffectedRows: 12, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var origHostSets []*TargetSet + if tt.setup != nil { + origHostSets = tt.setup(tt.args.target) + } + if tt.args.addToOrigHostSets { + origIds := make([]string, 0, len(origHostSets)) + for _, s := range origHostSets { + origIds = append(origIds, s.PublicId) + } + tt.args.hostSetIds = append(tt.args.hostSetIds, origIds...) + } + origTarget, lookedUpHs, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) + require.NoError(err) + assert.Equal(len(origHostSets), len(lookedUpHs)) + + got, _, affectedRows, err := repo.SetTargetHostSets(context.Background(), tt.args.target.GetPublicId(), tt.args.targetVersion, tt.args.hostSetIds, tt.args.opt...) + if tt.wantErr { + require.Error(err) + t.Log(err) + return + } + t.Log(err) + require.NoError(err) + assert.Equal(tt.wantAffectedRows, affectedRows) + assert.Equal(len(tt.args.hostSetIds), len(got)) + + var wantIds []string + wantIds = append(wantIds, tt.args.hostSetIds...) + sort.Strings(wantIds) + + var gotIds []string + if len(got) > 0 { + gotIds = make([]string, 0, len(got)) + for _, s := range got { + gotIds = append(gotIds, s.PublicId) + } + } + sort.Strings(gotIds) + assert.Equal(wantIds, gotIds) + + foundTarget, _, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) + require.NoError(err) + if tt.name != "no-change" { + assert.Equalf(tt.args.targetVersion+1, foundTarget.GetVersion(), "%s unexpected version: %d/%d", tt.name, tt.args.targetVersion+1, foundTarget.GetVersion()) + assert.Equalf(origTarget.GetVersion(), foundTarget.GetVersion()-1, "%s unexpected version: %d/%d", tt.name, origTarget.GetVersion(), foundTarget.GetVersion()-1) + } + t.Logf("target: %v and origVersion/newVersion: %d/%d", foundTarget.GetPublicId(), origTarget.GetVersion(), foundTarget.GetVersion()) + }) + } +} diff --git a/internal/target/repository_tcp_target.go b/internal/target/repository_tcp_target.go index 9903917aa..a9eb4eccc 100644 --- a/internal/target/repository_tcp_target.go +++ b/internal/target/repository_tcp_target.go @@ -13,59 +13,69 @@ import ( ) // CreateTcpTarget inserts into the repository and returns the new Target with -// its list of host sets. WithHostSets is currently the only supported option. -func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt ...Option) (Target, []*TargetSet, error) { +// its list of host sets and credential libraries. +// WithHostSets and WithCredentialLibraries are the only supported option. +func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt ...Option) (Target, []*TargetSet, []*TargetLibrary, error) { const op = "target.(Repository).CreateTcpTarget" opts := getOpts(opt...) if target == nil { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing target") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing target") } if target.TcpTarget == nil { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing target store") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing target store") } if target.ScopeId == "" { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing scope id") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing scope id") } if target.Name == "" { - return nil, nil, errors.New(errors.InvalidParameter, op, "missing name") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "missing name") } if target.PublicId != "" { - return nil, nil, errors.New(errors.InvalidParameter, op, "public id not empty") + return nil, nil, nil, errors.New(errors.InvalidParameter, op, "public id not empty") } t := target.Clone().(*TcpTarget) if opts.withPublicId != "" { if !strings.HasPrefix(opts.withPublicId, TcpTargetPrefix+"_") { - return nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", opts.withPublicId, TcpTargetPrefix)) + return nil, nil, nil, errors.New(errors.InvalidParameter, op, fmt.Sprintf("passed-in public ID %q has wrong prefix, should be %q", opts.withPublicId, TcpTargetPrefix)) } t.PublicId = opts.withPublicId } else { - id, err := newTcpTargetId() if err != nil { - return nil, nil, errors.Wrap(err, op) + return nil, nil, nil, errors.Wrap(err, op) } t.PublicId = id } oplogWrapper, err := r.kms.GetWrapper(ctx, target.ScopeId, kms.KeyPurposeOplog) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to get oplog wrapper")) } newHostSets := make([]interface{}, 0, len(opts.withHostSets)) for _, hsId := range opts.withHostSets { hostSet, err := NewTargetHostSet(t.PublicId, hsId) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target host set")) } newHostSets = append(newHostSets, hostSet) } + newCredLibs := make([]interface{}, 0, len(opts.withCredentialLibraries)) + for _, clId := range opts.withCredentialLibraries { + credLib, err := NewCredentialLibrary(t.PublicId, clId) + if err != nil { + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg("unable to create in memory target credential library")) + } + newCredLibs = append(newCredLibs, credLib) + } + metadata := t.oplog(oplog.OpType_OP_TYPE_CREATE) var returnedTarget interface{} var returnedHostSet []*TargetSet + var returnedCredLibs []*TargetLibrary _, err = r.writer.DoTx( ctx, db.StdRetryCnt, @@ -92,6 +102,16 @@ func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt } msgs = append(msgs, hostSetOplogMsgs...) } + if len(newCredLibs) > 0 { + credLibOplogMsgs := make([]*oplog.Message, 0, len(newCredLibs)) + if err := w.CreateItems(ctx, newCredLibs, db.NewOplogMsgs(&credLibOplogMsgs)); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to add credential libraries")) + } + if returnedCredLibs, err = fetchLibraries(ctx, read, t.PublicId); err != nil { + return errors.Wrap(err, op, errors.WithMsg("unable to read host sets")) + } + msgs = append(msgs, credLibOplogMsgs...) + } if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { return errors.Wrap(err, op, errors.WithMsg("unable to write oplog")) } @@ -100,9 +120,9 @@ func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt }, ) if err != nil { - return nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s target id", t.PublicId))) + return nil, nil, nil, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s target id", t.PublicId))) } - return returnedTarget.(*TcpTarget), returnedHostSet, nil + return returnedTarget.(*TcpTarget), returnedHostSet, returnedCredLibs, nil } // UpdateTcpTarget will update a target in the repository and return the written @@ -111,16 +131,16 @@ func (r *Repository) CreateTcpTarget(ctx context.Context, target *TcpTarget, opt // included in fieldMask. Name, Description, and WorkerFilter are the only // updatable fields. If no updatable fields are included in the fieldMaskPaths, // then an error is returned. -func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, version uint32, fieldMaskPaths []string, _ ...Option) (Target, []*TargetSet, int, error) { +func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, version uint32, fieldMaskPaths []string, _ ...Option) (Target, []*TargetSet, []*TargetLibrary, int, error) { const op = "target.(Repository).UpdateTcpTarget" if target == nil { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target") } if target.TcpTarget == nil { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target store") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target store") } if target.PublicId == "" { - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target public id") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidParameter, op, "missing target public id") } for _, f := range fieldMaskPaths { switch { @@ -131,7 +151,7 @@ func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, ver case strings.EqualFold("sessionconnectionlimit", f): case strings.EqualFold("workerfilter", f): default: - return nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.InvalidFieldMask, op, fmt.Sprintf("invalid field mask: %s", f)) } } var dbMask, nullFields []string @@ -148,11 +168,12 @@ func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, ver []string{"SessionMaxSeconds", "SessionConnectionLimit"}, ) if len(dbMask) == 0 && len(nullFields) == 0 { - return nil, nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "empty field mask") + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.EmptyFieldMask, op, "empty field mask") } var returnedTarget Target var rowsUpdated int var targetSets []*TargetSet + var credLibs []*TargetLibrary _, err := r.writer.DoTx( ctx, db.StdRetryCnt, @@ -160,7 +181,7 @@ func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, ver func(read db.Reader, w db.Writer) error { var err error t := target.Clone().(*TcpTarget) - returnedTarget, targetSets, rowsUpdated, err = r.update(ctx, t, version, dbMask, nullFields) + returnedTarget, targetSets, credLibs, rowsUpdated, err = r.update(ctx, t, version, dbMask, nullFields) if err != nil { return errors.Wrap(err, op) } @@ -169,9 +190,9 @@ func (r *Repository) UpdateTcpTarget(ctx context.Context, target *TcpTarget, ver ) if err != nil { if errors.IsUniqueError(err) { - return nil, nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("target %s already exists in scope %s", target.Name, target.ScopeId)) + return nil, nil, nil, db.NoRowsAffected, errors.New(errors.NotUnique, op, fmt.Sprintf("target %s already exists in scope %s", target.Name, target.ScopeId)) } - return nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", target.PublicId))) + return nil, nil, nil, db.NoRowsAffected, errors.Wrap(err, op, errors.WithMsg(fmt.Sprintf("failed for %s", target.PublicId))) } - return returnedTarget.(Target), targetSets, rowsUpdated, nil + return returnedTarget.(Target), targetSets, credLibs, rowsUpdated, nil } diff --git a/internal/target/repository_tcp_target_test.go b/internal/target/repository_tcp_target_test.go index 4de285ec1..563ad8689 100644 --- a/internal/target/repository_tcp_target_test.go +++ b/internal/target/repository_tcp_target_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" "github.com/hashicorp/boundary/internal/errors" @@ -34,6 +35,13 @@ func TestRepository_CreateTcpTarget(t *testing.T) { sets = append(sets, s.PublicId) } + cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) + var clIds []string + for _, cl := range credLibs { + clIds = append(clIds, cl.PublicId) + } + type args struct { target *TcpTarget opt []Option @@ -42,6 +50,7 @@ func TestRepository_CreateTcpTarget(t *testing.T) { name string args args wantHostSets []string + wantCredLibs []string wantErr bool wantIsError errors.Code }{ @@ -56,10 +65,64 @@ func TestRepository_CreateTcpTarget(t *testing.T) { require.NoError(t, err) return target }(), + }, + wantErr: false, + wantCredLibs: []string{}, + wantHostSets: []string{}, + }, + { + name: "valid-org-with-host-sets", + args: args{ + target: func() *TcpTarget { + target, err := NewTcpTarget(proj.PublicId, + WithName("valid-org-with-host-sets"), + WithDescription("valid-org"), + WithDefaultPort(uint32(22))) + require.NoError(t, err) + return target + }(), opt: []Option{WithHostSets(sets)}, }, wantErr: false, wantHostSets: sets, + wantCredLibs: []string{}, + }, + { + name: "valid-org-with-cred-libs", + args: args{ + target: func() *TcpTarget { + target, err := NewTcpTarget(proj.PublicId, + WithName("valid-org-with-cred-libs"), + WithDescription("valid-org"), + WithDefaultPort(uint32(22))) + require.NoError(t, err) + return target + }(), + opt: []Option{WithCredentialLibraries(clIds)}, + }, + wantErr: false, + wantCredLibs: clIds, + wantHostSets: []string{}, + }, + { + name: "valid-org-with-cred-libs-and-host-sets", + args: args{ + target: func() *TcpTarget { + target, err := NewTcpTarget(proj.PublicId, + WithName("valid-org-with-cred-libs-and-host-sets"), + WithDescription("valid-org"), + WithDefaultPort(uint32(22))) + require.NoError(t, err) + return target + }(), + opt: []Option{ + WithHostSets(sets), + WithCredentialLibraries(clIds), + }, + }, + wantErr: false, + wantCredLibs: clIds, + wantHostSets: sets, }, { name: "nil-target", @@ -112,7 +175,7 @@ func TestRepository_CreateTcpTarget(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - target, hostSets, err := repo.CreateTcpTarget(context.Background(), tt.args.target, tt.args.opt...) + target, hostSets, credLibs, err := repo.CreateTcpTarget(context.Background(), tt.args.target, tt.args.opt...) if tt.wantErr { assert.Error(err) assert.Nil(target) @@ -121,16 +184,23 @@ func TestRepository_CreateTcpTarget(t *testing.T) { } require.NoError(err) assert.NotNil(target.GetPublicId()) - gotIds := make([]string, 0, len(hostSets)) + hsIds := make([]string, 0, len(hostSets)) for _, s := range hostSets { - gotIds = append(gotIds, s.PublicId) + hsIds = append(hsIds, s.PublicId) } - assert.Equal(tt.wantHostSets, gotIds) + assert.Equal(tt.wantHostSets, hsIds) - foundTarget, foundHostSets, err := repo.LookupTarget(context.Background(), target.GetPublicId()) + clIds := make([]string, 0, len(credLibs)) + for _, cl := range credLibs { + clIds = append(clIds, cl.CredentialLibraryId) + } + assert.Equal(tt.wantCredLibs, clIds) + + foundTarget, foundHostSets, foundCredLibs, err := repo.LookupTarget(context.Background(), target.GetPublicId()) assert.NoError(err) assert.True(proto.Equal(target.(*TcpTarget), foundTarget.(*TcpTarget))) assert.Equal(hostSets, foundHostSets) + assert.Equal(credLibs, foundCredLibs) err = db.TestVerifyOplog(t, rw, target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) @@ -347,8 +417,10 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { wantIsError: errors.NotUnique, }, } - for _, tt := range tests { + css := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) + for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { + cs := css[i] require, assert := require.New(t), assert.New(t) if tt.wantDup { _ = TestTcpTarget(t, conn, proj.PublicId, tt.args.name) @@ -360,7 +432,14 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { for _, hs := range hsets { testHostSetIds = append(testHostSetIds, hs.PublicId) } - tt.newTargetOpts = append(tt.newTargetOpts, WithHostSets(testHostSetIds)) + + cls := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 5) + var testClIds []string + for _, cl := range cls { + testClIds = append(testClIds, cl.PublicId) + } + + tt.newTargetOpts = append(tt.newTargetOpts, WithHostSets(testHostSetIds), WithCredentialLibraries(testClIds)) name := tt.newName if name == "" { name = testId(t) @@ -376,7 +455,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { updateTarget.Description = tt.args.description updateTarget.DefaultPort = tt.args.port - targetAfterUpdate, hostSets, updatedRows, err := repo.UpdateTcpTarget(context.Background(), &updateTarget, target.Version, tt.args.fieldMaskPaths, tt.args.opt...) + targetAfterUpdate, hostSets, credLibs, updatedRows, err := repo.UpdateTcpTarget(context.Background(), &updateTarget, target.Version, tt.args.fieldMaskPaths, tt.args.opt...) if tt.wantErr { assert.Error(err) assert.True(errors.Match(errors.T(tt.wantIsError), err)) @@ -397,13 +476,19 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { } assert.Equal(testHostSetIds, afterUpdateIds) + afterUpdateIds = make([]string, 0, len(credLibs)) + for _, cl := range credLibs { + afterUpdateIds = append(afterUpdateIds, cl.CredentialLibraryId) + } + assert.Equal(testClIds, afterUpdateIds) + switch tt.name { case "valid-no-op": assert.Equal(target.UpdateTime, targetAfterUpdate.(*TcpTarget).UpdateTime) default: assert.NotEqual(target.UpdateTime, targetAfterUpdate.(*TcpTarget).UpdateTime) } - foundTarget, _, err := repo.LookupTarget(context.Background(), target.PublicId) + foundTarget, _, _, err := repo.LookupTarget(context.Background(), target.PublicId) assert.NoError(err) assert.True(proto.Equal(targetAfterUpdate.((*TcpTarget)), foundTarget.((*TcpTarget)))) dbassert := dbassert.New(t, conn.DB()) diff --git a/internal/target/repository_test.go b/internal/target/repository_test.go index 4b20d25ee..7530bd73a 100644 --- a/internal/target/repository_test.go +++ b/internal/target/repository_test.go @@ -3,20 +3,17 @@ package target import ( "context" "fmt" - "sort" "strconv" "testing" "time" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" - "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" ) func TestNewRepository(t *testing.T) { @@ -206,7 +203,7 @@ func TestRepository_LookupTarget(t *testing.T) { if tt.scopeName != "" { opts = append(opts, WithScopeName(tt.scopeName)) } - got, _, err := repo.LookupTarget(context.Background(), id, opts...) + got, _, _, err := repo.LookupTarget(context.Background(), id, opts...) if tt.wantErr { require.Error(err) return @@ -420,7 +417,7 @@ func TestRepository_DeleteTarget(t *testing.T) { } assert.NoError(err) assert.Equal(tt.wantRowsDeleted, deletedRows) - foundGroup, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) + foundGroup, _, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) assert.NoError(err) assert.Nil(foundGroup) @@ -429,478 +426,3 @@ func TestRepository_DeleteTarget(t *testing.T) { }) } } - -func TestRepository_AddTargetHostSets(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - staticOrg, staticProj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - - createHostSetsFn := func(orgs, projects []string) []string { - results := []string{} - for _, publicId := range orgs { - cats := static.TestCatalogs(t, conn, publicId, 1) - hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) - results = append(results, hsets[0].PublicId) - } - for _, publicId := range projects { - cats := static.TestCatalogs(t, conn, publicId, 1) - hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) - results = append(results, hsets[0].PublicId) - } - return results - } - - type args struct { - targetVersion uint32 - wantTargetIds bool - opt []Option - } - tests := []struct { - name string - args args - wantErr bool - wantErrIs error - }{ - { - name: "valid", - args: args{ - targetVersion: 1, - wantTargetIds: true, - }, - wantErr: false, - }, - { - name: "bad-version", - args: args{ - targetVersion: 1000, - wantTargetIds: true, - }, - wantErr: true, - }, - { - name: "zero-version", - args: args{ - targetVersion: 0, - wantTargetIds: true, - }, - wantErr: true, - }, - { - name: "no-host-sets", - args: args{ - targetVersion: 1, - }, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - require.NoError(conn.Where("1=1").Delete(allocTargetHostSet()).Error) - require.NoError(conn.Where("1=1").Delete(allocTcpTarget()).Error) - - projTarget := TestTcpTarget(t, conn, staticProj.PublicId, "static-proj") - - var hostSetIds []string - origTarget, origHostSet, err := repo.LookupTarget(context.Background(), projTarget.PublicId) - require.NoError(err) - require.Equal(0, len(origHostSet)) - - if tt.args.wantTargetIds { - hostSetIds = createHostSetsFn([]string{staticOrg.PublicId}, []string{staticProj.PublicId}) - } - - gotTarget, gotHostSets, err := repo.AddTargetHostSets(context.Background(), projTarget.PublicId, tt.args.targetVersion, hostSetIds, tt.args.opt...) - if tt.wantErr { - require.Error(err) - if tt.wantErrIs != nil { - assert.Truef(errors.Is(err, tt.wantErrIs), "unexpected error %s", err.Error()) - } - // test to see of the target version update oplog was not created - err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) - assert.Error(err) - - // TODO (jimlambrt 9/2020) - unfortunately, we can currently - // test to make sure that the oplog entry for a target create - // doesn't exist because the db.TestVerifyOplog doesn't really - // support that level of testing and the previous call to - // TestTcpTarget would create an oplog entry for the - // create on the target. Once TestVerifyOplog supports the - // appropriate granularity, we should add an appropriate assert. - - return - } - require.NoError(err) - gotHostSet := map[string]*TargetSet{} - for _, s := range gotHostSets { - gotHostSet[s.PublicId] = s - } - - // TODO (jimlambrt 9/2020) - unfortunately, we can currently - // test to make sure that the oplog entry for a target create - // doesn't exist because the db.TestVerifyOplog doesn't really - // support that level of testing and the previous call to - // TestTcpTarget would create an oplog entry for the - // create on the target. Once TestVerifyOplog supports the - // appropriate granularity, we should add an appropriate assert. - - // test to see of the target version update oplog was created - err = db.TestVerifyOplog(t, rw, projTarget.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second)) - assert.NoError(err) - - foundHostSets, err := fetchSets(context.Background(), rw, projTarget.PublicId) - require.NoError(err) - for _, s := range foundHostSets { - assert.NotEmpty(gotHostSet[s.PublicId]) - } - - target, ths, err := repo.LookupTarget(context.Background(), projTarget.PublicId) - require.NoError(err) - assert.Equal(tt.args.targetVersion+1, target.GetVersion()) - assert.Equal(origTarget.GetVersion(), target.GetVersion()-1) - assert.Equal(gotHostSets, ths) - assert.True(proto.Equal(gotTarget.(*TcpTarget), target.(*TcpTarget))) - }) - } -} - -func TestRepository_DeleteTargetHosts(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj := iam.TestScopes(t, iamRepo) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - - type args struct { - target Target - targetIdOverride *string - targetVersionOverride *uint32 - createCnt int - deleteCnt int - opt []Option - } - tests := []struct { - name string - args args - wantRowsDeleted int - wantErr bool - wantIsErr errors.Code - }{ - { - name: "valid", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "valid"), - createCnt: 5, - deleteCnt: 5, - }, - wantRowsDeleted: 5, - wantErr: false, - }, - { - name: "valid-keeping-some", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "valid-keeping-some"), - createCnt: 5, - deleteCnt: 2, - }, - wantRowsDeleted: 2, - wantErr: false, - }, - { - name: "no-deletes", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "no-deletes"), - createCnt: 5, - }, - wantRowsDeleted: 0, - wantErr: true, - wantIsErr: errors.InvalidParameter, - }, - { - name: "not-found", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "not-found"), - targetIdOverride: func() *string { id := testId(t); return &id }(), - createCnt: 5, - deleteCnt: 5, - }, - wantRowsDeleted: 0, - wantErr: true, - }, - { - name: "missing-target-id", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "missing-target-id"), - targetIdOverride: func() *string { id := ""; return &id }(), - createCnt: 5, - deleteCnt: 5, - }, - wantRowsDeleted: 0, - wantErr: true, - wantIsErr: errors.InvalidParameter, - }, - { - name: "zero-version", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "zero-version"), - targetVersionOverride: func() *uint32 { v := uint32(0); return &v }(), - createCnt: 5, - deleteCnt: 5, - }, - wantRowsDeleted: 0, - wantErr: true, - wantIsErr: errors.InvalidParameter, - }, - { - name: "bad-version", - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "bad-version"), - targetVersionOverride: func() *uint32 { v := uint32(1000); return &v }(), - createCnt: 5, - deleteCnt: 5, - }, - wantRowsDeleted: 0, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - hsIds := make([]string, 0, tt.args.createCnt) - if tt.args.createCnt > 0 { - for i := 0; i < tt.args.createCnt; i++ { - cats := static.TestCatalogs(t, conn, proj.PublicId, 1) - hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) - hsIds = append(hsIds, hsets[0].PublicId) - } - } - _, addedHostSets, err := repo.AddTargetHostSets(context.Background(), tt.args.target.GetPublicId(), 1, hsIds, tt.args.opt...) - require.NoError(err) - assert.Equal(tt.args.createCnt, len(addedHostSets)) - - deleteHostSets := make([]string, 0, tt.args.deleteCnt) - for i := 0; i < tt.args.deleteCnt; i++ { - deleteHostSets = append(deleteHostSets, hsIds[i]) - } - var targetId string - switch { - case tt.args.targetIdOverride != nil: - targetId = *tt.args.targetIdOverride - default: - targetId = tt.args.target.GetPublicId() - } - var targetVersion uint32 - switch { - case tt.args.targetVersionOverride != nil: - targetVersion = *tt.args.targetVersionOverride - default: - targetVersion = 2 - } - deletedRows, err := repo.DeleteTargeHostSets(context.Background(), targetId, targetVersion, deleteHostSets, tt.args.opt...) - if tt.wantErr { - assert.Error(err) - assert.Equal(0, deletedRows) - assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "unexpected error %s", err.Error()) - // TODO (jimlambrt 9/2020) - unfortunately, we can currently - // test to make sure that the oplog entry for a target update - // doesn't exist because the db.TestVerifyOplog doesn't really - // support that level of testing and the previous call to - // repo.AddTargetHostSets() would create an oplog entry for the - // update to the target. Once TestVerifyOplog supports the - // appropriate granularity, we should add an appropriate assert. - - err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) - assert.Error(err) - assert.True(errors.IsNotFoundError(err)) - return - } - require.NoError(err) - assert.Equal(tt.wantRowsDeleted, deletedRows) - - // TODO (jimlambrt 9/2020) - unfortunately, we can currently - // test to make sure that the oplog entry for a target update - // doesn't exist because the db.TestVerifyOplog doesn't really - // support that level of testing and the previous call to - // repo.AddTargetHostSets() would create an oplog entry for the - // update to the target. Once TestVerifyOplog supports the - // appropriate granularity,, we should add an appropriate assert. - - // we should find the oplog for the delete of target host sets - err = db.TestVerifyOplog(t, rw, tt.args.target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) - assert.NoError(err) - }) - } -} - -func TestRepository_SetTargetHostSets(t *testing.T) { - t.Parallel() - conn, _ := db.TestSetup(t, "postgres") - rw := db.New(conn) - wrapper := db.TestWrapper(t) - testKms := kms.TestKms(t, conn, wrapper) - repo, err := NewRepository(rw, rw, testKms) - require.NoError(t, err) - - iamRepo := iam.TestRepo(t, conn, wrapper) - _, proj := iam.TestScopes(t, iamRepo) - - testCats := static.TestCatalogs(t, conn, proj.PublicId, 1) - hsets := static.TestSets(t, conn, testCats[0].GetPublicId(), 5) - testHostSetIds := make([]string, 0, len(hsets)) - for _, hs := range hsets { - testHostSetIds = append(testHostSetIds, hs.PublicId) - } - - createHostSetsFn := func() []string { - results := []string{} - for i := 0; i < 10; i++ { - cats := static.TestCatalogs(t, conn, proj.PublicId, 1) - hsets := static.TestSets(t, conn, cats[0].GetPublicId(), 1) - results = append(results, hsets[0].PublicId) - } - return results - } - - setupFn := func(target Target) []*TargetSet { - hs := createHostSetsFn() - _, created, err := repo.AddTargetHostSets(context.Background(), target.GetPublicId(), 1, hs) - require.NoError(t, err) - require.Equal(t, 10, len(created)) - return created - } - type args struct { - target Target - targetVersion uint32 - hostSetIds []string - addToOrigHostSets bool - opt []Option - } - tests := []struct { - name string - setup func(Target) []*TargetSet - args args - wantAffectedRows int - wantErr bool - }{ - { - name: "clear", - setup: setupFn, - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "clear"), - targetVersion: 2, // yep, since setupFn will increment it to 2 - hostSetIds: []string{}, - }, - wantErr: false, - wantAffectedRows: 10, - }, - { - name: "no-change", - setup: setupFn, - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "no-change"), - targetVersion: 2, // yep, since setupFn will increment it to 2 - hostSetIds: []string{}, - addToOrigHostSets: true, - }, - wantErr: false, - wantAffectedRows: 0, - }, - { - name: "add-sets", - setup: setupFn, - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "add-sets"), - targetVersion: 2, // yep, since setupFn will increment it to 2 - hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, - addToOrigHostSets: true, - }, - wantErr: false, - wantAffectedRows: 2, - }, - { - name: "add host sets with zero version", - setup: setupFn, - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "add host sets with zero version"), - targetVersion: 0, - hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, - addToOrigHostSets: true, - }, - wantErr: true, - }, - { - name: "remove existing and add users and grps", - setup: setupFn, - args: args{ - target: TestTcpTarget(t, conn, proj.PublicId, "remove existing and add users and grps"), - targetVersion: 2, // yep, since setupFn will increment it to 2 - hostSetIds: []string{testHostSetIds[0], testHostSetIds[1]}, - addToOrigHostSets: false, - }, - wantErr: false, - wantAffectedRows: 12, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - var origHostSets []*TargetSet - if tt.setup != nil { - origHostSets = tt.setup(tt.args.target) - } - if tt.args.addToOrigHostSets { - origIds := make([]string, 0, len(origHostSets)) - for _, s := range origHostSets { - origIds = append(origIds, s.PublicId) - } - tt.args.hostSetIds = append(tt.args.hostSetIds, origIds...) - } - origTarget, lookedUpHs, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) - require.NoError(err) - assert.Equal(len(origHostSets), len(lookedUpHs)) - - got, affectedRows, err := repo.SetTargetHostSets(context.Background(), tt.args.target.GetPublicId(), tt.args.targetVersion, tt.args.hostSetIds, tt.args.opt...) - if tt.wantErr { - require.Error(err) - t.Log(err) - return - } - t.Log(err) - require.NoError(err) - assert.Equal(tt.wantAffectedRows, affectedRows) - assert.Equal(len(tt.args.hostSetIds), len(got)) - - var wantIds []string - wantIds = append(wantIds, tt.args.hostSetIds...) - sort.Strings(wantIds) - - var gotIds []string - if len(got) > 0 { - gotIds = make([]string, 0, len(got)) - for _, s := range got { - gotIds = append(gotIds, s.PublicId) - } - } - sort.Strings(gotIds) - assert.Equal(wantIds, gotIds) - - foundTarget, _, err := repo.LookupTarget(context.Background(), tt.args.target.GetPublicId()) - require.NoError(err) - if tt.name != "no-change" { - assert.Equalf(tt.args.targetVersion+1, foundTarget.GetVersion(), "%s unexpected version: %d/%d", tt.name, tt.args.targetVersion+1, foundTarget.GetVersion()) - assert.Equalf(origTarget.GetVersion(), foundTarget.GetVersion()-1, "%s unexpected version: %d/%d", tt.name, origTarget.GetVersion(), foundTarget.GetVersion()-1) - } - t.Logf("target: %v and origVersion/newVersion: %d/%d", foundTarget.GetPublicId(), origTarget.GetVersion(), foundTarget.GetVersion()) - }) - } -} diff --git a/internal/target/store/target.pb.go b/internal/target/store/target.pb.go index 880980aa6..4e62b27a0 100644 --- a/internal/target/store/target.pb.go +++ b/internal/target/store/target.pb.go @@ -404,6 +404,85 @@ func (x *TcpTarget) GetWorkerFilter() string { return "" } +type CredentialLibrary struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // target_id of the Target + // @inject_tag: gorm:"primary_key" + TargetId string `protobuf:"bytes,10,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"primary_key"` + // credential_library_id of the CredentialLibrary + // @inject_tag: gorm:"primary_key" + CredentialLibraryId string `protobuf:"bytes,20,opt,name=credential_library_id,json=credentialLibraryId,proto3" json:"credential_library_id,omitempty" gorm:"primary_key"` + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + CredentialPurpose string `protobuf:"bytes,30,opt,name=credential_purpose,json=credentialPurpose,proto3" json:"credential_purpose,omitempty" gorm:"primary_key"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *CredentialLibrary) Reset() { + *x = CredentialLibrary{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialLibrary) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialLibrary) ProtoMessage() {} + +func (x *CredentialLibrary) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + 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 CredentialLibrary.ProtoReflect.Descriptor instead. +func (*CredentialLibrary) Descriptor() ([]byte, []int) { + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{3} +} + +func (x *CredentialLibrary) GetTargetId() string { + if x != nil { + return x.TargetId + } + return "" +} + +func (x *CredentialLibrary) GetCredentialLibraryId() string { + if x != nil { + return x.CredentialLibraryId + } + return "" +} + +func (x *CredentialLibrary) GetCredentialPurpose() string { + if x != nil { + return x.CredentialPurpose + } + return "" +} + +func (x *CredentialLibrary) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + var File_controller_storage_target_store_v1_target_proto protoreflect.FileDescriptor var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ @@ -503,12 +582,26 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xc2, 0xdd, 0x29, 0x1d, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 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, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, - 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x22, 0xe0, + 0x01, 0x0a, 0x11, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, + 0x72, 0x61, 0x72, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, + 0x64, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x13, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4c, 0x69, 0x62, 0x72, + 0x61, 0x72, 0x79, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, + 0x70, 0x6f, 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, + 0x69, 0x6d, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, + 0x65, 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, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -523,24 +616,26 @@ func file_controller_storage_target_store_v1_target_proto_rawDescGZIP() []byte { return file_controller_storage_target_store_v1_target_proto_rawDescData } -var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 4) var file_controller_storage_target_store_v1_target_proto_goTypes = []interface{}{ (*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView (*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet (*TcpTarget)(nil), // 2: controller.storage.target.store.v1.TcpTarget - (*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp + (*CredentialLibrary)(nil), // 3: controller.storage.target.store.v1.CredentialLibrary + (*timestamp.Timestamp)(nil), // 4: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_target_store_v1_target_proto_depIdxs = []int32{ - 3, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 3: controller.storage.target.store.v1.TcpTarget.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 4: controller.storage.target.store.v1.TcpTarget.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 5, // [5:5] is the sub-list for method output_type - 5, // [5:5] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name + 4, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // 3: controller.storage.target.store.v1.TcpTarget.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // 4: controller.storage.target.store.v1.TcpTarget.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // 5: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_controller_storage_target_store_v1_target_proto_init() } @@ -585,6 +680,18 @@ func file_controller_storage_target_store_v1_target_proto_init() { return nil } } + file_controller_storage_target_store_v1_target_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialLibrary); 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{ @@ -592,7 +699,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_target_store_v1_target_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 4, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/target/testing.go b/internal/target/testing.go index c30411ee8..544dc8eb5 100644 --- a/internal/target/testing.go +++ b/internal/target/testing.go @@ -35,6 +35,16 @@ func TestTcpTarget(t *testing.T, conn *gorm.DB, scopeId, name string, opt ...Opt err := rw.CreateItems(context.Background(), newHostSets) require.NoError(err) } + if len(opts.withCredentialLibraries) > 0 { + newCredLibs := make([]interface{}, 0, len(opts.withCredentialLibraries)) + for _, cl := range opts.withCredentialLibraries { + newCl, err := NewCredentialLibrary(target.PublicId, cl) + require.NoError(err) + newCredLibs = append(newCredLibs, newCl) + } + err := rw.CreateItems(context.Background(), newCredLibs) + require.NoError(err) + } return target } @@ -49,3 +59,16 @@ func testId(t *testing.T) string { require.NoError(t, err) return id } + +// TestCredentialLibrary creates a CredentialLibrary for targetId and +// libraryId. +func TestCredentialLibrary(t *testing.T, conn *gorm.DB, targetId, libraryId string) *CredentialLibrary { + t.Helper() + require := require.New(t) + rw := db.New(conn) + lib, err := NewCredentialLibrary(targetId, libraryId) + require.NoError(err) + err = rw.Create(context.Background(), lib) + require.NoError(err) + return lib +} diff --git a/internal/target/testing_test.go b/internal/target/testing_test.go index 63e2590da..5b3a330f2 100644 --- a/internal/target/testing_test.go +++ b/internal/target/testing_test.go @@ -4,9 +4,11 @@ import ( "context" "testing" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -36,3 +38,33 @@ func Test_TestTcpTarget(t *testing.T) { } require.Equal(sets, foundIds) } + +func Test_TestCredentialLibrary(t *testing.T) { + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + _, proj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + + target := TestTcpTarget(t, conn, proj.PublicId, t.Name()) + store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + vlibs := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + var libIds []string + var libs []*CredentialLibrary + for _, v := range vlibs { + libIds = append(libIds, v.GetPublicId()) + lib := TestCredentialLibrary(t, conn, target.GetPublicId(), v.GetPublicId()) + require.NotNil(lib) + libs = append(libs, lib) + } + + assert.Len(libs, 2) + + rw := db.New(conn) + foundLibs, err := fetchLibraries(context.Background(), rw, target.PublicId) + require.NoError(err) + foundIds := make([]string, 0, len(foundLibs)) + for _, s := range foundLibs { + foundIds = append(foundIds, s.CredentialLibraryId) + } + require.Equal(libIds, foundIds) +} diff --git a/internal/tests/api/credentiallibraries/credentiallibrary_test.go b/internal/tests/api/credentiallibraries/credentiallibrary_test.go new file mode 100644 index 000000000..a0fdc0cc4 --- /dev/null +++ b/internal/tests/api/credentiallibraries/credentiallibrary_test.go @@ -0,0 +1,190 @@ +package credentiallibraries_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/servers/controller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + vaultServ := vault.NewTestVaultServer(t) + _, vaultTok := vaultServ.CreateToken(t) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + cs, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + require.NotNil(cs) + + lClient := credentiallibraries.NewClient(client) + + ul, err := lClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.Empty(ul.Items) + + var expected []*credentiallibraries.CredentialLibrary + for i := 0; i < 10; i++ { + expected = append(expected, &credentiallibraries.CredentialLibrary{Name: fmt.Sprint(i), Attributes: map[string]interface{}{"vault_path": "something"}}) + } + + cl, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName(expected[0].Name), credentiallibraries.WithVaultCredentialLibraryPath("something")) + require.NoError(err) + expected[0] = cl.Item + + ul, err = lClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableSetSlice(expected[:1]), comparableSetSlice(ul.Items)) + + for i := 1; i < 10; i++ { + cl, err = lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName(expected[i].Name), credentiallibraries.WithVaultCredentialLibraryPath("something")) + require.NoError(err) + expected[i] = cl.Item + } + ul, err = lClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableSetSlice(expected), comparableSetSlice(ul.Items)) + + filterItem := ul.Items[3] + ul, err = lClient.List(tc.Context(), cs.Item.Id, + credentiallibraries.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id))) + require.NoError(err) + assert.Len(ul.Items, 1) + assert.Equal(filterItem.Id, ul.Items[0].Id) +} + +func comparableSetSlice(in []*credentiallibraries.CredentialLibrary) []credentiallibraries.CredentialLibrary { + var filtered []credentiallibraries.CredentialLibrary + for _, i := range in { + p := credentiallibraries.CredentialLibrary{ + Id: i.Id, + Name: i.Name, + Description: i.Description, + CreatedTime: i.CreatedTime, + UpdatedTime: i.UpdatedTime, + } + filtered = append(filtered, p) + } + return filtered +} + +func TestCrud(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + _, vaultTok := vaultServ.CreateToken(t) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + cs, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + require.NotNil(cs) + + checkResource := func(t *testing.T, step string, r *credentiallibraries.CredentialLibrary, err error, wantedName string, wantVersion uint32) { + t.Helper() + require.NoError(err, step) + assert.NotNil(r, "returned no resource", step) + gotName := "" + if r.Name != "" { + gotName = r.Name + } + assert.Equal(wantedName, gotName, step) + assert.Equal(wantVersion, r.Version) + } + + lClient := credentiallibraries.NewClient(client) + + r, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo"), + credentiallibraries.WithVaultCredentialLibraryPath("something")) + checkResource(t, "create", r.Item, err, "foo", 1) + + r, err = lClient.Read(tc.Context(), r.Item.Id) + checkResource(t, "read", r.Item, err, "foo", 1) + + r, err = lClient.Update(tc.Context(), r.Item.Id, r.Item.Version, credentiallibraries.WithName("bar")) + checkResource(t, "update", r.Item, err, "bar", 2) + + r, err = lClient.Update(tc.Context(), r.Item.Id, r.Item.Version, credentiallibraries.DefaultName()) + checkResource(t, "update", r.Item, err, "", 3) + + _, err = lClient.Delete(tc.Context(), r.Item.Id) + assert.NoError(err) + _, err = lClient.Delete(tc.Context(), r.Item.Id) + require.Error(err) + apiErr := api.AsServerError(err) + require.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) +} + +// TODO: Get better coverage for expected errors and error formats. +func TestErrors(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + _, vaultTok := vaultServ.CreateToken(t) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + cs, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + require.NotNil(cs) + + lClient := credentiallibraries.NewClient(client) + + l, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo"), + credentiallibraries.WithVaultCredentialLibraryPath("something")) + require.NoError(err) + assert.NotNil(l) + + // Updating the wrong version should fail. + _, err = lClient.Update(tc.Context(), l.Item.Id, 73, credentiallibraries.WithName("anything")) + require.Error(err) + apiErr := api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + l, err = lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithName("foo")) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.Nil(l) + + _, err = lClient.Read(tc.Context(), vault.CredentialLibraryPrefix+"_doesntexis") + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + _, err = lClient.Read(tc.Context(), "invalid id") + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode()) +} diff --git a/internal/tests/api/credentialstores/credentialstore_test.go b/internal/tests/api/credentialstores/credentialstore_test.go new file mode 100644 index 000000000..0ba0d3535 --- /dev/null +++ b/internal/tests/api/credentialstores/credentialstore_test.go @@ -0,0 +1,187 @@ +package credentialstores_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/servers/controller" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestList(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + storeClient := credentialstores.NewClient(client) + + ul, err := storeClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.Empty(ul.Items) + + var expected []*credentialstores.CredentialStore + for i := 0; i < 10; i++ { + _, tok := vaultServ.CreateToken(t) + expected = append(expected, &credentialstores.CredentialStore{Name: fmt.Sprint(i), Attributes: map[string]interface{}{ + "address": vaultServ.Addr, + "token": tok, + }}) + } + + cr, err := storeClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName(expected[0].Name), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(expected[0].Attributes["token"].(string))) + require.NoError(err) + expected[0] = cr.Item + + ul, err = storeClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items)) + + for i := 1; i < 10; i++ { + cr, err = storeClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName(expected[i].Name), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(expected[i].Attributes["token"].(string))) + require.NoError(err) + expected[i] = cr.Item + } + ul, err = storeClient.List(tc.Context(), proj.GetPublicId()) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected), comparableCatalogSlice(ul.Items)) + + filterItem := ul.Items[3] + ul, err = storeClient.List(tc.Context(), proj.GetPublicId(), + credentialstores.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id))) + require.NoError(err) + assert.Len(ul.Items, 1) + assert.Equal(filterItem.Id, ul.Items[0].Id) +} + +func comparableCatalogSlice(in []*credentialstores.CredentialStore) []credentialstores.CredentialStore { + var filtered []credentialstores.CredentialStore + for _, i := range in { + p := credentialstores.CredentialStore{ + Id: i.Id, + Name: i.Name, + Description: i.Description, + CreatedTime: i.CreatedTime, + UpdatedTime: i.UpdatedTime, + Attributes: i.Attributes, + } + filtered = append(filtered, p) + } + return filtered +} + +func TestCrud(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + _, vaultTok := vaultServ.CreateToken(t) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + + checkResource := func(step string, cs *credentialstores.CredentialStore, err error, wantedName string, wantVersion uint32) { + assert.NotNil(cs, "returned no resource", step) + gotName := "" + if cs.Name != "" { + gotName = cs.Name + } + assert.Equal(wantedName, gotName, step) + assert.Equal(wantVersion, cs.Version) + } + + csClient := credentialstores.NewClient(client) + + cs, err := csClient.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo"), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + require.NotNil(cs) + checkResource("create", cs.Item, err, "foo", 1) + + cs, err = csClient.Read(tc.Context(), cs.Item.Id) + checkResource("read", cs.Item, err, "foo", 1) + + cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.WithName("bar")) + checkResource("update", cs.Item, err, "bar", 2) + + cs, err = csClient.Update(tc.Context(), cs.Item.Id, cs.Item.Version, credentialstores.DefaultName()) + checkResource("update", cs.Item, err, "", 3) + + _, err = csClient.Delete(tc.Context(), cs.Item.Id) + assert.NoError(err) + + _, err = csClient.Delete(tc.Context(), cs.Item.Id) + require.Error(err) + apiErr := api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) +} + +func TestErrors(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + _, vaultTok := vaultServ.CreateToken(t) + + client := tc.Client() + token := tc.Token() + client.SetToken(token.Token) + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + c := credentialstores.NewClient(client) + + vs, err := c.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo"), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + apiErr := api.AsServerError(err) + assert.Nil(apiErr) + assert.NotNil(vs) + + // Updating the wrong version should fail. + _, err = c.Update(tc.Context(), vs.Item.Id, 73, credentialstores.WithName("anything")) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + // same token + _, err = c.Create(tc.Context(), "vault", proj.GetPublicId(), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + + // Same name + _, err = c.Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithName("foo")) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + + _, err = c.Read(tc.Context(), vault.CredentialStorePrefix+"_doesntexis") + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + _, err = c.Read(tc.Context(), "invalid id") + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode()) +} diff --git a/internal/tests/api/targets/target_test.go b/internal/tests/api/targets/target_test.go index ab668f7bf..23f73a14b 100644 --- a/internal/tests/api/targets/target_test.go +++ b/internal/tests/api/targets/target_test.go @@ -6,9 +6,12 @@ import ( "testing" "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/api/credentialstores" "github.com/hashicorp/boundary/api/hostcatalogs" "github.com/hashicorp/boundary/api/hostsets" "github.com/hashicorp/boundary/api/targets" + "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/servers/controller" "github.com/hashicorp/boundary/internal/target" @@ -16,7 +19,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestCustom(t *testing.T) { +func TestHostSetASD(t *testing.T) { assert, require := assert.New(t), require.New(t) tc := controller.NewTestController(t, nil) defer tc.Shutdown() @@ -59,6 +62,60 @@ func TestCustom(t *testing.T) { assert.Empty(tar.Item.HostSetIds) } +func defaultCredLibrary(r *credentiallibraries.CredentialLibrary) *targets.CredentialLibrary { + return &targets.CredentialLibrary{Id: r.Id, Purpose: "application", CredentialStoreId: r.CredentialStoreId} +} + +func TestCredentialLibraryASD(t *testing.T) { + assert, require := assert.New(t), require.New(t) + tc := controller.NewTestController(t, nil) + defer tc.Shutdown() + vaultServ := vault.NewTestVaultServer(t, vault.WithTestVaultTLS(vault.TestNoTLS)) + _, vaultTok := vaultServ.CreateToken(t) + + token := tc.Token() + _, proj := iam.TestScopes(t, tc.IamRepo(), iam.WithUserId(token.UserId)) + client := tc.Client().Clone() + client.SetToken(token.Token) + + cs, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), + credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) + require.NoError(err) + require.NotNil(cs) + + lClient := credentiallibraries.NewClient(client) + r1, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1")) + require.NoError(err) + require.NotNil(r1) + + r2, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2")) + require.NoError(err) + require.NotNil(r1) + + tarClient := targets.NewClient(client) + tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo")) + require.NoError(err) + require.NotNil(tar) + assert.Empty(tar.Item.CredentialLibraryIds) + + tar, err = tarClient.AddCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, []string{r1.Item.Id}) + require.NoError(err) + require.NotNil(tar) + assert.ElementsMatch(tar.Item.CredentialLibraryIds, []string{r1.Item.Id}) + assert.ElementsMatch(tar.Item.CredentialLibraries, []*targets.CredentialLibrary{defaultCredLibrary(r1.Item)}) + + tar, err = tarClient.SetCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, []string{r2.Item.Id}) + require.NoError(err) + require.NotNil(tar) + assert.ElementsMatch(tar.Item.CredentialLibraryIds, []string{r2.Item.Id}) + assert.ElementsMatch(tar.Item.CredentialLibraries, []*targets.CredentialLibrary{defaultCredLibrary(r2.Item)}) + + tar, err = tarClient.RemoveCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, []string{r2.Item.Id}) + require.NoError(err) + require.NotNil(tar) + assert.Empty(tar.Item.CredentialLibraryIds) +} + func TestList(t *testing.T) { assert, require := assert.New(t), require.New(t) tc := controller.NewTestController(t, nil) diff --git a/internal/types/action/action.go b/internal/types/action/action.go index 0c5e68a98..e805d7df1 100644 --- a/internal/types/action/action.go +++ b/internal/types/action/action.go @@ -8,80 +8,86 @@ type Type uint // not using iota intentionally, since the values are stored in the db as well. const ( - Unknown Type = 0 - List Type = 1 - Create Type = 2 - Update Type = 3 - Read Type = 4 - Delete Type = 5 - Authenticate Type = 6 - All Type = 7 - AuthorizeSession Type = 8 - AddGrants Type = 9 - RemoveGrants Type = 10 - SetGrants Type = 11 - AddPrincipals Type = 12 - SetPrincipals Type = 13 - RemovePrincipals Type = 14 - Deauthenticate Type = 15 - AddMembers Type = 16 - SetMembers Type = 17 - RemoveMembers Type = 18 - SetPassword Type = 19 - ChangePassword Type = 20 - AddHosts Type = 21 - SetHosts Type = 22 - RemoveHosts Type = 23 - AddHostSets Type = 24 - SetHostSets Type = 25 - RemoveHostSets Type = 26 - Cancel Type = 27 - AddAccounts Type = 28 - SetAccounts Type = 29 - RemoveAccounts Type = 30 - ReadSelf Type = 31 - CancelSelf Type = 32 - ChangeState Type = 33 - DeleteSelf Type = 34 - NoOp Type = 35 + Unknown Type = 0 + List Type = 1 + Create Type = 2 + Update Type = 3 + Read Type = 4 + Delete Type = 5 + Authenticate Type = 6 + All Type = 7 + AuthorizeSession Type = 8 + AddGrants Type = 9 + RemoveGrants Type = 10 + SetGrants Type = 11 + AddPrincipals Type = 12 + SetPrincipals Type = 13 + RemovePrincipals Type = 14 + Deauthenticate Type = 15 + AddMembers Type = 16 + SetMembers Type = 17 + RemoveMembers Type = 18 + SetPassword Type = 19 + ChangePassword Type = 20 + AddHosts Type = 21 + SetHosts Type = 22 + RemoveHosts Type = 23 + AddHostSets Type = 24 + SetHostSets Type = 25 + RemoveHostSets Type = 26 + Cancel Type = 27 + AddAccounts Type = 28 + SetAccounts Type = 29 + RemoveAccounts Type = 30 + ReadSelf Type = 31 + CancelSelf Type = 32 + ChangeState Type = 33 + DeleteSelf Type = 34 + NoOp Type = 35 + AddCredentialLibraries Type = 36 + SetCredentialLibraries Type = 37 + RemoveCredentialLibraries Type = 38 ) var Map = map[string]Type{ - Create.String(): Create, - List.String(): List, - Update.String(): Update, - Read.String(): Read, - Delete.String(): Delete, - Authenticate.String(): Authenticate, - All.String(): All, - AuthorizeSession.String(): AuthorizeSession, - AddGrants.String(): AddGrants, - RemoveGrants.String(): RemoveGrants, - SetGrants.String(): SetGrants, - AddPrincipals.String(): AddPrincipals, - SetPrincipals.String(): SetPrincipals, - RemovePrincipals.String(): RemovePrincipals, - Deauthenticate.String(): Deauthenticate, - AddMembers.String(): AddMembers, - SetMembers.String(): SetMembers, - RemoveMembers.String(): RemoveMembers, - SetPassword.String(): SetPassword, - ChangePassword.String(): ChangePassword, - AddHosts.String(): AddHosts, - SetHosts.String(): SetHosts, - RemoveHosts.String(): RemoveHosts, - AddHostSets.String(): AddHostSets, - SetHostSets.String(): SetHostSets, - RemoveHostSets.String(): RemoveHostSets, - Cancel.String(): Cancel, - AddAccounts.String(): AddAccounts, - SetAccounts.String(): SetAccounts, - RemoveAccounts.String(): RemoveAccounts, - ReadSelf.String(): ReadSelf, - CancelSelf.String(): CancelSelf, - ChangeState.String(): ChangeState, - DeleteSelf.String(): DeleteSelf, - NoOp.String(): NoOp, + Create.String(): Create, + List.String(): List, + Update.String(): Update, + Read.String(): Read, + Delete.String(): Delete, + Authenticate.String(): Authenticate, + All.String(): All, + AuthorizeSession.String(): AuthorizeSession, + AddGrants.String(): AddGrants, + RemoveGrants.String(): RemoveGrants, + SetGrants.String(): SetGrants, + AddPrincipals.String(): AddPrincipals, + SetPrincipals.String(): SetPrincipals, + RemovePrincipals.String(): RemovePrincipals, + Deauthenticate.String(): Deauthenticate, + AddMembers.String(): AddMembers, + SetMembers.String(): SetMembers, + RemoveMembers.String(): RemoveMembers, + SetPassword.String(): SetPassword, + ChangePassword.String(): ChangePassword, + AddHosts.String(): AddHosts, + SetHosts.String(): SetHosts, + RemoveHosts.String(): RemoveHosts, + AddHostSets.String(): AddHostSets, + SetHostSets.String(): SetHostSets, + RemoveHostSets.String(): RemoveHostSets, + Cancel.String(): Cancel, + AddAccounts.String(): AddAccounts, + SetAccounts.String(): SetAccounts, + RemoveAccounts.String(): RemoveAccounts, + ReadSelf.String(): ReadSelf, + CancelSelf.String(): CancelSelf, + ChangeState.String(): ChangeState, + DeleteSelf.String(): DeleteSelf, + NoOp.String(): NoOp, + AddCredentialLibraries.String(): AddCredentialLibraries, + SetCredentialLibraries.String(): SetCredentialLibraries, + RemoveCredentialLibraries.String(): RemoveCredentialLibraries, } func (a Type) String() string { @@ -122,6 +128,9 @@ func (a Type) String() string { "change-state", "delete:self", "no-op", + "add-credential-libraries", + "set-credential-libraries", + "remove-credential-libraries", }[a] } diff --git a/internal/types/resource/resource.go b/internal/types/resource/resource.go index 834e0b567..ceb989ce7 100644 --- a/internal/types/resource/resource.go +++ b/internal/types/resource/resource.go @@ -4,23 +4,25 @@ package resource type Type uint const ( - Unknown Type = 0 - All Type = 1 - Scope Type = 2 - User Type = 3 - Group Type = 4 - Role Type = 5 - AuthMethod Type = 6 - Account Type = 7 - AuthToken Type = 8 - HostCatalog Type = 9 - HostSet Type = 10 - Host Type = 11 - Target Type = 12 - Controller Type = 13 - Worker Type = 14 - Session Type = 15 - ManagedGroup Type = 16 + Unknown Type = iota + All + Scope + User + Group + Role + AuthMethod + Account + AuthToken + HostCatalog + HostSet + Host + Target + Controller + Worker + Session + ManagedGroup + CredentialStore + CredentialLibrary ) func (r Type) String() string { @@ -42,25 +44,29 @@ func (r Type) String() string { "worker", "session", "managed-group", + "credential-store", + "credential-library", }[r] } var Map = map[string]Type{ - Unknown.String(): Unknown, - All.String(): All, - Scope.String(): Scope, - User.String(): User, - Group.String(): Group, - Role.String(): Role, - AuthMethod.String(): AuthMethod, - Account.String(): Account, - AuthToken.String(): AuthToken, - HostCatalog.String(): HostCatalog, - HostSet.String(): HostSet, - Host.String(): Host, - Target.String(): Target, - Controller.String(): Controller, - Worker.String(): Worker, - Session.String(): Session, - ManagedGroup.String(): ManagedGroup, + Unknown.String(): Unknown, + All.String(): All, + Scope.String(): Scope, + User.String(): User, + Group.String(): Group, + Role.String(): Role, + AuthMethod.String(): AuthMethod, + Account.String(): Account, + AuthToken.String(): AuthToken, + HostCatalog.String(): HostCatalog, + HostSet.String(): HostSet, + Host.String(): Host, + Target.String(): Target, + Controller.String(): Controller, + Worker.String(): Worker, + Session.String(): Session, + ManagedGroup.String(): ManagedGroup, + CredentialStore.String(): CredentialStore, + CredentialLibrary.String(): CredentialLibrary, } diff --git a/internal/types/resource/resource_test.go b/internal/types/resource/resource_test.go index b7485a8f3..947f28353 100644 --- a/internal/types/resource/resource_test.go +++ b/internal/types/resource/resource_test.go @@ -79,6 +79,14 @@ func Test_Resource(t *testing.T) { typeString: "managed-group", want: ManagedGroup, }, + { + typeString: "credential-store", + want: CredentialStore, + }, + { + typeString: "credential-library", + want: CredentialLibrary, + }, } for _, tt := range tests { t.Run(tt.typeString, func(t *testing.T) { diff --git a/testing/controller/docs.go b/testing/controller/docs.go index b316b0a17..9aa628b50 100644 --- a/testing/controller/docs.go +++ b/testing/controller/docs.go @@ -1,2 +1,2 @@ -// controller is a package meant for internal testing only. The interfaces may change or be removed at any time without warning. +// Package controller is a package meant for internal testing only. The interfaces may change or be removed at any time without warning. package controller diff --git a/testing/vault/docs.go b/testing/vault/docs.go new file mode 100644 index 000000000..31ff43b6b --- /dev/null +++ b/testing/vault/docs.go @@ -0,0 +1,2 @@ +// Package vault is a package meant for internal testing only. The interfaces may change or be removed at any time without warning. +package vault diff --git a/testing/vault/vault.go b/testing/vault/vault.go new file mode 100644 index 000000000..8521df648 --- /dev/null +++ b/testing/vault/vault.go @@ -0,0 +1,109 @@ +package vault + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/credential/vault" +) + +// TestVaultServer is a vault server running in a docker container suitable +// for testing. +type TestVaultServer struct { + *vault.TestVaultServer +} + +// NewTestVaultServer creates and returns a TestVaultServer. Some Vault +// secret engines require the Vault server be created with a docker +// network. Check the Mount method for the Vault secret engine to see if a +// docker network is required. +// +// WithTestVaultTLS and WithDockerNetwork are the only valid options. +// Setting the WithDockerNetwork option can significantly increase the +// amount of time required for a test to run. +func NewTestVaultServer(t *testing.T, opt ...TestOption) *TestVaultServer { + t.Helper() + + opts := getTestOpts(t, opt...) + var internalOpts []vault.TestOption + switch opts.vaultTLS { + case TestNoTLS: + internalOpts = append(internalOpts, vault.WithTestVaultTLS(vault.TestNoTLS)) + case TestServerTLS: + internalOpts = append(internalOpts, vault.WithTestVaultTLS(vault.TestServerTLS)) + case TestClientTLS: + internalOpts = append(internalOpts, vault.WithTestVaultTLS(vault.TestClientTLS)) + } + internalOpts = append(internalOpts, vault.WithDockerNetwork(opts.dockerNetwork)) + + ts := vault.NewTestVaultServer(t, internalOpts...) + return &TestVaultServer{ts} +} + +// TestVaultTLS represents the TLS configuration level of a +// TestVaultServer. +type TestVaultTLS int + +const ( + // TestNoTLS disables TLS. The test server Addr begins with http://. + TestNoTLS TestVaultTLS = iota // no TLS + + // TestServerTLS configures the Vault test server listener to use TLS. + // A CA certificate is generated and a server certificate is issued + // from the CA certificate. The CA certificate is available in the + // CaCert field of the TestVaultServer. The test server Addr begins + // with https://. + TestServerTLS + + // TestClientTLS configures the Vault test server listener to require a + // client certificate for mTLS and includes all of the settings from + // TestServerTLS. A second CA certificate is generated and a client + // certificate is issued from this CA certificate. The client + // certificate and the client certificate key are available in the in + // the ClientCert and ClientKey fields of the TestVaultServer + // respectively. + TestClientTLS +) + +// TestOption - how Options are passed as arguments. +type TestOption func(*testing.T, *testOptions) + +// options = how options are represented +type testOptions struct { + vaultTLS TestVaultTLS + dockerNetwork bool +} + +func getTestOpts(t *testing.T, opt ...TestOption) testOptions { + t.Helper() + opts := getDefaultTestOptions(t) + for _, o := range opt { + o(t, &opts) + } + return opts +} + +func getDefaultTestOptions(t *testing.T) testOptions { + t.Helper() + return testOptions{ + vaultTLS: TestNoTLS, + dockerNetwork: false, + } +} + +// WithTestVaultTLS sets the Vault TLS option. +// TestNoTLS is the default TLS option. +func WithTestVaultTLS(s TestVaultTLS) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.vaultTLS = s + } +} + +// WithDockerNetwork sets the option to create docker network when creating +// a Vault test server. The default is to not create a docker network. +func WithDockerNetwork(b bool) TestOption { + return func(t *testing.T, o *testOptions) { + t.Helper() + o.dockerNetwork = b + } +} diff --git a/website/content/docs/api-clients/desktop.mdx b/website/content/docs/api-clients/desktop.mdx index 9e89e2f99..ba9d4878b 100644 --- a/website/content/docs/api-clients/desktop.mdx +++ b/website/content/docs/api-clients/desktop.mdx @@ -35,16 +35,19 @@ $ boundary dev + ![](/img/desktop/macos/boundary-desktop-open.png) + ![](/img/desktop/windows/boundary-desktop-open.png) + 1. You'll be prompted for the Boundary server origin, this is the URL for the client to connect to the Boundary API. If you are running a local `dev` mode server, this URL will be `http://localhost:9200` @@ -52,48 +55,57 @@ $ boundary dev + ![](/img/desktop/macos/boundary-desktop-origin.png) + ![](/img/desktop/windows/boundary-desktop-origin.png) + 1. You can now login to Boundary. We're using a `dev` mode server in this example with the username `admin` and the password `password` + ![](/img/desktop/macos/boundary-desktop-login.png) + ![](/img/desktop/windows/boundary-desktop-login.png) + 1. After logging in, you should see the targets your user is authorized to connect to. Since we are using a `dev` mode server we see the default generated target for `127.0.0.1:22` + ![](/img/desktop/macos/boundary-desktop-landing.png) + ![](/img/desktop/windows/boundary-desktop-landing.png) + ### Connect! -> The rest of this example assumes you're running Boundary in `dev` mode. @@ -104,32 +116,38 @@ $ boundary dev + ![](/img/desktop/macos/boundary-desktop-connect.png) + ![](/img/desktop/windows/boundary-desktop-connect.png) + 1. Navigate to the `Sessions` pane and you'll see this session is in `pending` state because we haven't made a connection to it yet (but will!) + ![](/img/desktop/macos/boundary-desktop-pending.png) + ![](/img/desktop/windows/boundary-desktop-pending.png) + -> The next step assumes you have a SSH server running that the default target will connect to. 1. On the CLI, `ssh` to the target using the local ephemeral port created in the previous step @@ -150,29 +168,35 @@ $ + ![](/img/desktop/macos/boundary-desktop-active.png) + ![](/img/desktop/windows/boundary-desktop-active.png) + 1. Click `Cancel` to cancel the session and you'll see the status go to `canceling` briefly, then `terminated` + ![](/img/desktop/macos/boundary-desktop-terminated.png) + ![](/img/desktop/windows/boundary-desktop-terminated.png) + 1. Navigate back to the CLI and you'll see your SSH session has closed diff --git a/website/content/docs/common-workflows/manage-users-groups.mdx b/website/content/docs/common-workflows/manage-users-groups.mdx index 9b7d5a920..543932c12 100644 --- a/website/content/docs/common-workflows/manage-users-groups.mdx +++ b/website/content/docs/common-workflows/manage-users-groups.mdx @@ -47,6 +47,7 @@ Create a password-type auth method in the generated default org: + ```bash $ boundary auth-methods create password -scope-id o_1234567890 -name org_auth_method -description 'Org auth method' @@ -73,6 +74,7 @@ Auth Method information: + 1. Navigate to an org, then to auth methods. 1. Choose the **New** button. 1. Fill in the auth method details. @@ -88,6 +90,7 @@ Auth Method information: + ```hcl resource "boundary_auth_method" "password" { name = "org_auth_method" @@ -100,6 +103,7 @@ resource "boundary_auth_method" "password" { + ## Create Account Create an account for the org-scoped auth method. Note that by default, user @@ -110,6 +114,7 @@ the attributes for the auth method, if desired.) + ```bash boundary accounts create password -auth-method-id ampw_PbE6nNT72a -login-name 'myuser' -password supersecure -name my_account -description 'My password account' @@ -136,6 +141,7 @@ Account information: + 1. Navigate to orgs > auth methods > auth method, then to the **Accounts** tab. 1. Choose the **New** button. 1. Fill the account details. @@ -151,6 +157,7 @@ Account information: + ```hcl resource "boundary_account" "my_user" { name = "myuser" @@ -165,6 +172,7 @@ resource "boundary_account" "my_user" { + ## Create a User Next, create a user at the org scope. @@ -172,6 +180,7 @@ Next, create a user at the org scope. + First, create the user resource: ```bash @@ -219,6 +228,7 @@ User information: + 1. Navigate to an org, then to users. 1. Choose the **New** button. 1. Fill the user details. @@ -248,6 +258,7 @@ Then associate the user with an account: + ```hcl resource "boundary_user" "myuser" { name = "myuser" @@ -262,6 +273,7 @@ resource "boundary_user" "myuser" { + ## Login Now you can test logging in. @@ -269,6 +281,7 @@ Now you can test logging in. + ```bash $ boundary authenticate password -login-name myuser -password supersecure -auth-method-id ampw_PbE6nNT72a ``` @@ -276,6 +289,7 @@ $ boundary authenticate password -login-name myuser -password supersecure -auth- + 1. Navigate to the authenticate page. 1. Choose an org scope with which to authenticate. 1. Fill the credentials. @@ -291,6 +305,7 @@ $ boundary authenticate password -login-name myuser -password supersecure -auth- + ```hcl provider "boundary" { addr = "http://127.0.0.1:9200" @@ -302,3 +317,4 @@ provider "boundary" { + diff --git a/website/content/docs/getting-started/run-and-login.mdx b/website/content/docs/getting-started/run-and-login.mdx index f3a517fbb..b22ebec6f 100644 --- a/website/content/docs/getting-started/run-and-login.mdx +++ b/website/content/docs/getting-started/run-and-login.mdx @@ -22,6 +22,7 @@ dev mode. These can be overridden, or randomly generated, with flags to + ``` $ boundary authenticate password \ -login-name=admin \ @@ -62,6 +63,7 @@ to track it and voice your thoughts. + To authenticate to the Admin UI, open [http://127.0.0.1:9200](http://127.0.0.1:9200) in a browser and enter the login name and password: @@ -75,9 +77,11 @@ name and password: +

+ To authenticate using the Boundary desktop client, launch Boundary desktop client and connect to boundary to using [http://127.0.0.1:9200](http://127.0.0.1:9200) and enter the login name and password: @@ -98,8 +102,10 @@ and enter the login name and password:

+ + You can also use these overrides to configure the [Terraform provider for Boundary](https://github.com/hashicorp/terraform-provider-boundary): @@ -118,6 +124,7 @@ the Boundary server does not use TLS in development mode.
+ ## Next Steps See [connecting to your first target](/docs/getting-started/connect-to-target) for how diff --git a/website/content/docs/installing/no-gen-resources.mdx b/website/content/docs/installing/no-gen-resources.mdx index 1a72a9828..99d01017f 100644 --- a/website/content/docs/installing/no-gen-resources.mdx +++ b/website/content/docs/installing/no-gen-resources.mdx @@ -60,6 +60,7 @@ To authenticate to Boundary using the recovery KMS workflow: + To use the recovery workflow on the CLI, you must pass the `-recovery-config ` flag or set the environment variable for `BOUNDARY_RECOVERY_CONFIG` for every command ran. Authentication takes place for every command ran when using the recovery workflow, there is no `boundary authenticate` step: @@ -81,6 +82,7 @@ $ boundary users create -recovery-config /tmp/recovery.hcl + To configure your provider to use the recovery KMS workflow, provide the KMS block as the value for `recovery_kms_hcl`: @@ -101,6 +103,7 @@ EOT + ## Initialize the Database Before you can start Boundary, the database must be initialized. It's useful to look at the help output for the init command: @@ -137,6 +140,7 @@ for each scope. We're going to specify a role for managing these scopes by selec + ```bash $ boundary scopes create -name 'org' -scope-id 'global' \ -recovery-config /tmp/recovery.hcl \ @@ -154,6 +158,7 @@ $ boundary scopes create -name 'project' -scope-id + ```hcl resource "boundary_scope" "org" { scope_id = "global" @@ -176,6 +181,7 @@ resource "boundary_scope" "project" { + ### Create an Auth Method Create an auth method in the organization scope. @@ -183,6 +189,7 @@ Create an auth method in the organization scope. + ```bash $ boundary auth-methods create password \ -recovery-config /tmp/recovery.hcl \ @@ -194,6 +201,7 @@ $ boundary auth-methods create password \ + ```hcl resource "boundary_auth_method" "password" { name = "my_password_auth_method" @@ -206,6 +214,7 @@ resource "boundary_auth_method" "password" { + ### Create a Login Account Create a login account for the auth method. @@ -213,6 +222,7 @@ Create a login account for the auth method. + ```bash $ boundary accounts create password \ -recovery-config /tmp/recovery.hcl \ @@ -224,6 +234,7 @@ $ boundary accounts create password \ + ```hcl resource "boundary_account" "myuser" { name = "myuser" @@ -238,6 +249,7 @@ resource "boundary_account" "myuser" { + ### Create a User Create a user and associate the user with the login account created in the previous step. @@ -246,6 +258,7 @@ This user will also be the principal in the role we create in the following step + ```bash $ boundary users create -scope-id \ -recovery-config /tmp/recovery.hcl \ @@ -261,6 +274,7 @@ $ boundary users add-accounts \ + ```hcl resource "boundary_user" "myuser" { name = "myuser" @@ -273,6 +287,7 @@ resource "boundary_user" "myuser" { + ### Create Roles to Manage Scopes The following describes the four baseline roles you'll need to create to manage resources within the org and project @@ -291,6 +306,7 @@ The following example creates 4 roles: + Assumes recovery key export from above steps is still set: ```bash @@ -313,6 +329,7 @@ $ boundary roles add-principals -id \ + ```hcl resource "boundary_role" "global_anon_listing" { scope_id = "global" @@ -328,11 +345,13 @@ resource "boundary_role" "global_anon_listing" { + #### Anonymous listing role for org scope + Assumes recovery key export from above steps is still set: ```bash @@ -354,6 +373,7 @@ $ boundary roles add-principals -id \ + ```hcl resource "boundary_role" "org_anon_listing" { scope_id = boundary_scope.org.id @@ -369,11 +389,13 @@ resource "boundary_role" "org_anon_listing" { + #### Org admin role for myuser + Assumes recovery key export from above steps is still set: ```bash @@ -394,6 +416,7 @@ $ boundary roles add-principals -id \ + ```hcl resource "boundary_role" "org_admin" { scope_id = "global" @@ -408,11 +431,13 @@ resource "boundary_role" "org_admin" { + #### Project admin for myuser + Assumes recovery key export from above steps is still set: ```bash @@ -433,6 +458,7 @@ $ boundary roles add-principals -id \ + ```hcl resource "boundary_role" "project_admin" { scope_id = boundary_scope.org.id @@ -447,6 +473,7 @@ resource "boundary_role" "project_admin" { + ### Login as Your New User ```bash