From 58d546cdd4057bcf12b76f9816356a15262c18d4 Mon Sep 17 00:00:00 2001 From: Louis Ruch Date: Wed, 18 May 2022 20:20:45 -0700 Subject: [PATCH] feat(credential): Add static credential store and username_password credential --- Makefile | 3 + api/credentials/credential.gen.go | 325 +++++++ api/credentials/option.gen.go | 135 +++ .../username_password_attributes.gen.go | 8 + internal/api/genapi/input.go | 65 +- internal/cmd/commands.go | 61 ++ .../credentialscmd/credentials.gen.go | 273 ++++++ internal/cmd/commands/credentialscmd/funcs.go | 177 ++++ .../username_password_credentials.gen.go | 258 +++++ .../credentialscmd/username_password_funcs.go | 97 ++ .../cmd/commands/credentialstorescmd/funcs.go | 10 +- .../static_credentialstores.gen.go | 261 +++++ .../credentialstorescmd/static_funcs.go | 33 + internal/cmd/gencli/input.go | 42 + internal/credential/credential.go | 10 +- .../credential/static/credential_store.go | 65 ++ .../static/credential_store_test.go | 111 +++ internal/credential/static/doc.go | 2 + internal/credential/static/fields.go | 9 + internal/credential/static/options.go | 47 + internal/credential/static/options_test.go | 29 + internal/credential/static/public_ids.go | 44 + internal/credential/static/repository.go | 49 + .../static/repository_credential.go | 309 ++++++ .../static/repository_credential_store.go | 237 +++++ .../repository_credential_store_test.go | 746 +++++++++++++++ .../static/repository_credential_test.go | 896 ++++++++++++++++++ internal/credential/static/repository_test.go | 119 +++ internal/credential/static/store/static.pb.go | 467 +++++++++ internal/credential/static/testing.go | 131 +++ internal/credential/static/testing_test.go | 86 ++ .../static/usernamepassword_credential.go | 121 +++ .../usernamepassword_credential_test.go | 178 ++++ internal/credential/vault/private_library.go | 2 +- .../vault/repository_credentials_test.go | 6 +- internal/daemon/controller/common/common.go | 26 +- internal/daemon/controller/controller.go | 29 +- internal/daemon/controller/handler.go | 13 +- .../credentials/credential_service.go | 592 ++++++++++++ .../credentials/credential_service_test.go | 742 +++++++++++++++ .../credentialstore_service.go | 315 ++++-- .../credentialstore_service_test.go | 505 +++++++++- internal/gen/controller.swagger.json | 246 +++++ .../api/services/credential_service.pb.go | 864 +++++++++++++++++ .../api/services/credential_service.pb.gw.go | 632 ++++++++++++ .../services/credential_service_grpc.pb.go | 287 ++++++ internal/perms/grants_test.go | 2 +- .../resources/credentials/v1/credential.proto | 102 ++ .../api/services/v1/credential_service.proto | 127 +++ .../credential/static/store/v1/static.proto | 118 +++ .../credentials_classification_test.go | 201 ++++ .../tests/api/credentials/credentials_test.go | 208 ++++ .../credentialstores/credentialstore_test.go | 7 +- .../cli/boundary/_credential_stores.bash | 30 + internal/tests/cli/boundary/_credentials.bash | 34 + .../tests/cli/boundary/credential_stores.bats | 47 + internal/tests/cli/boundary/credentials.bats | 60 ++ internal/types/resource/resource.go | 3 + .../resources/credentials/credential.pb.go | 462 +++++++++ 59 files changed, 10859 insertions(+), 205 deletions(-) create mode 100644 api/credentials/credential.gen.go create mode 100644 api/credentials/option.gen.go create mode 100644 api/credentials/username_password_attributes.gen.go create mode 100644 internal/cmd/commands/credentialscmd/credentials.gen.go create mode 100644 internal/cmd/commands/credentialscmd/funcs.go create mode 100644 internal/cmd/commands/credentialscmd/username_password_credentials.gen.go create mode 100644 internal/cmd/commands/credentialscmd/username_password_funcs.go create mode 100644 internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go create mode 100644 internal/cmd/commands/credentialstorescmd/static_funcs.go create mode 100644 internal/credential/static/credential_store.go create mode 100644 internal/credential/static/credential_store_test.go create mode 100644 internal/credential/static/doc.go create mode 100644 internal/credential/static/fields.go create mode 100644 internal/credential/static/options.go create mode 100644 internal/credential/static/options_test.go create mode 100644 internal/credential/static/public_ids.go create mode 100644 internal/credential/static/repository.go create mode 100644 internal/credential/static/repository_credential.go create mode 100644 internal/credential/static/repository_credential_store.go create mode 100644 internal/credential/static/repository_credential_store_test.go create mode 100644 internal/credential/static/repository_credential_test.go create mode 100644 internal/credential/static/repository_test.go create mode 100644 internal/credential/static/store/static.pb.go create mode 100644 internal/credential/static/testing.go create mode 100644 internal/credential/static/testing_test.go create mode 100644 internal/credential/static/usernamepassword_credential.go create mode 100644 internal/credential/static/usernamepassword_credential_test.go create mode 100644 internal/daemon/controller/handlers/credentials/credential_service.go create mode 100644 internal/daemon/controller/handlers/credentials/credential_service_test.go create mode 100644 internal/gen/controller/api/services/credential_service.pb.go create mode 100644 internal/gen/controller/api/services/credential_service.pb.gw.go create mode 100644 internal/gen/controller/api/services/credential_service_grpc.pb.go create mode 100644 internal/proto/controller/api/resources/credentials/v1/credential.proto create mode 100644 internal/proto/controller/api/services/v1/credential_service.proto create mode 100644 internal/proto/controller/storage/credential/static/store/v1/static.proto create mode 100644 internal/tests/api/credentials/credentials_classification_test.go create mode 100644 internal/tests/api/credentials/credentials_test.go create mode 100644 internal/tests/cli/boundary/_credential_stores.bash create mode 100644 internal/tests/cli/boundary/_credentials.bash create mode 100644 internal/tests/cli/boundary/credential_stores.bats create mode 100644 internal/tests/cli/boundary/credentials.bats create mode 100644 sdk/pbs/controller/api/resources/credentials/credential.pb.go diff --git a/Makefile b/Makefile index 2b1a239aa..266328b8c 100644 --- a/Makefile +++ b/Makefile @@ -155,6 +155,7 @@ protobuild: @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 + @protoc-go-inject-tag -input=./internal/credential/static/store/static.pb.go @protoc-go-inject-tag -input=./internal/servers/servers.pb.go @protoc-go-inject-tag -input=./internal/kms/store/audit_key.pb.go @@ -184,6 +185,8 @@ protobuild: @protoc-go-inject-tag -input=./internal/gen/controller/api/services/credential_store_service.pb.go @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/credentiallibraries/credential_library.pb.go @protoc-go-inject-tag -input=./internal/gen/controller/api/services/credential_library_service.pb.go + @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/credentials/credential.pb.go + @protoc-go-inject-tag -input=./internal/gen/controller/api/services/credential_service.pb.go @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/roles/role.pb.go @protoc-go-inject-tag -input=./internal/gen/controller/api/services/role_service.pb.go @protoc-go-inject-tag -input=./sdk/pbs/controller/api/resources/sessions/session.pb.go diff --git a/api/credentials/credential.gen.go b/api/credentials/credential.gen.go new file mode 100644 index 000000000..30bcd3657 --- /dev/null +++ b/api/credentials/credential.gen.go @@ -0,0 +1,325 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentials + +import ( + "context" + "errors" + "fmt" + "net/url" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/scopes" +) + +type Credential 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 CredentialReadResult struct { + Item *Credential + response *api.Response +} + +func (n CredentialReadResult) GetItem() interface{} { + return n.Item +} + +func (n CredentialReadResult) GetResponse() *api.Response { + return n.response +} + +type CredentialCreateResult = CredentialReadResult +type CredentialUpdateResult = CredentialReadResult + +type CredentialDeleteResult struct { + response *api.Response +} + +// GetItem will always be nil for CredentialDeleteResult +func (n CredentialDeleteResult) GetItem() interface{} { + return nil +} + +func (n CredentialDeleteResult) GetResponse() *api.Response { + return n.response +} + +type CredentialListResult struct { + Items []*Credential + response *api.Response +} + +func (n CredentialListResult) GetItems() interface{} { + return n.Items +} + +func (n CredentialListResult) 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, credentialStoreId string, opt ...Option) (*CredentialCreateResult, 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") + } + if resourceType == "" { + return nil, fmt.Errorf("empty resourceType value passed into Create request") + } else { + opts.postMap["type"] = resourceType + } + + opts.postMap["credential_store_id"] = credentialStoreId + + req, err := c.client.NewRequest(ctx, "POST", "credentials", 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(CredentialCreateResult) + target.Item = new(Credential) + 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) (*CredentialReadResult, 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("credentials/%s", url.PathEscape(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(CredentialReadResult) + target.Item = new(Credential) + 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) (*CredentialUpdateResult, 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("credentials/%s", url.PathEscape(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(CredentialUpdateResult) + target.Item = new(Credential) + 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) (*CredentialDeleteResult, 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("credentials/%s", url.PathEscape(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 := &CredentialDeleteResult{ + response: resp, + } + return target, nil +} + +func (c *Client) List(ctx context.Context, credentialStoreId string, opt ...Option) (*CredentialListResult, 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", "credentials", 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(CredentialListResult) + 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/credentials/option.gen.go b/api/credentials/option.gen.go new file mode 100644 index 000000000..ac95b9e24 --- /dev/null +++ b/api/credentials/option.gen.go @@ -0,0 +1,135 @@ +package credentials + +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 WithName(inName string) Option { + return func(o *options) { + o.postMap["name"] = inName + } +} + +func DefaultName() Option { + return func(o *options) { + o.postMap["name"] = nil + } +} + +func WithUsernamePasswordCredentialPassword(inPassword string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["password"] = inPassword + o.postMap["attributes"] = val + } +} + +func WithUsernamePasswordCredentialUsername(inUsername string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = interface{}(map[string]interface{}{}) + } + val := raw.(map[string]interface{}) + val["username"] = inUsername + o.postMap["attributes"] = val + } +} diff --git a/api/credentials/username_password_attributes.gen.go b/api/credentials/username_password_attributes.gen.go new file mode 100644 index 000000000..e0923d0fc --- /dev/null +++ b/api/credentials/username_password_attributes.gen.go @@ -0,0 +1,8 @@ +// Code generated by "make api"; DO NOT EDIT. +package credentials + +type UsernamePasswordAttributes struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + PasswordHmac string `json:"password_hmac,omitempty"` +} diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index 7db6a6d5d..0ae65b2e7 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/groups" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs" @@ -435,15 +436,24 @@ var inputStructs = []*structInfo{ versionEnabled: true, createResponseTypes: true, }, - // Credentials { - inProto: &credentialstores.VaultCredentialStoreAttributes{}, - outFile: "credentialstores/vault_credential_store_attributes.gen.go", - subtypeName: "VaultCredentialStore", + inProto: &credentials.UsernamePasswordAttributes{}, + outFile: "credentials/username_password_attributes.gen.go", + subtypeName: "UsernamePasswordCredential", + fieldOverrides: []fieldInfo{ + { + Name: "Username", + SkipDefault: true, + }, + { + Name: "Password", + SkipDefault: true, + }, + }, }, { - inProto: &credentialstores.CredentialStore{}, - outFile: "credentialstores/credential_store.gen.go", + inProto: &credentials.Credential{}, + outFile: "credentials/credential.gen.go", templates: []*template.Template{ clientTemplate, createTemplate, @@ -452,50 +462,13 @@ var inputStructs = []*structInfo{ 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", + pluralResourceName: "credentials", parentTypeName: "credential-store", versionEnabled: true, createResponseTypes: true, + typeOnCreate: true, }, + // Host related resources { inProto: &hostcatalogs.HostCatalog{}, diff --git a/internal/cmd/commands.go b/internal/cmd/commands.go index eb54312c5..0a78ba796 100644 --- a/internal/cmd/commands.go +++ b/internal/cmd/commands.go @@ -9,6 +9,7 @@ import ( "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/credentialscmd" "github.com/hashicorp/boundary/internal/cmd/commands/credentialstorescmd" "github.com/hashicorp/boundary/internal/cmd/commands/database" "github.com/hashicorp/boundary/internal/cmd/commands/dev" @@ -410,6 +411,12 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "create", }, nil }, + "credential-stores create static": func() (cli.Command, error) { + return &credentialstorescmd.StaticCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, "credential-stores update": func() (cli.Command, error) { return &credentialstorescmd.Command{ Command: base.NewCommand(ui), @@ -422,6 +429,60 @@ func initCommands(ui, serverCmdUi cli.Ui, runOpts *RunOptions) { Func: "update", }, nil }, + "credential-stores update static": func() (cli.Command, error) { + return &credentialstorescmd.StaticCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + + "credentials": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + }, nil + }, + "credentials read": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + Func: "read", + }, nil + }, + "credentials delete": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + Func: "delete", + }, nil + }, + "credentials list": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + Func: "list", + }, nil + }, + "credentials create": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credentials create username-password": func() (cli.Command, error) { + return &credentialscmd.UsernamePasswordCommand{ + Command: base.NewCommand(ui), + Func: "create", + }, nil + }, + "credentials update": func() (cli.Command, error) { + return &credentialscmd.Command{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, + "credentials update username-password": func() (cli.Command, error) { + return &credentialscmd.UsernamePasswordCommand{ + Command: base.NewCommand(ui), + Func: "update", + }, nil + }, "groups": func() (cli.Command, error) { return &groupscmd.Command{ diff --git a/internal/cmd/commands/credentialscmd/credentials.gen.go b/internal/cmd/commands/credentialscmd/credentials.gen.go new file mode 100644 index 000000000..7726a2e80 --- /dev/null +++ b/internal/cmd/commands/credentialscmd/credentials.gen.go @@ -0,0 +1,273 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentialscmd + +import ( + "errors" + "fmt" + "sync" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func 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" + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *Command) Help() string { + initFlags() + + var helpStr string + helpMap := common.HelpMap("credential") + + 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", 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" + switch c.Func { + case "list": + c.plural = "credentials" + } + + 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 []credentials.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 c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + credentialsClient := credentials.NewClient(client) + + if c.FlagFilter != "" { + opts = append(opts, credentials.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 = credentialsClient.Read(c.Context, c.FlagId, opts...) + + case "delete": + result, err = credentialsClient.Delete(c.Context, c.FlagId, opts...) + + case "list": + listResult, err = credentialsClient.List(c.Context, c.FlagCredentialStoreId, opts...) + + } + + result, err = executeExtraActions(c, result, err, credentialsClient, 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().([]*credentials.Credential) + 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, *[]credentials.Option) bool { return true } + executeExtraActions = func(_ *Command, inResult api.GenericResult, inErr error, _ *credentials.Client, _ uint32, _ []credentials.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomActionOutput = func(*Command) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialscmd/funcs.go b/internal/cmd/commands/credentialscmd/funcs.go new file mode 100644 index 000000000..ca3bd25f4 --- /dev/null +++ b/internal/cmd/commands/credentialscmd/funcs.go @@ -0,0 +1,177 @@ +package credentialscmd + +import ( + "fmt" + "time" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentials" + "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 credentials [sub command] [options] [args]", + "", + " This command allows operations on Boundary credential resources. Example:", + "", + " Read a credential:", + "", + ` $ boundary credentials read -id cred_1234567890`, + "", + " Please see the credentials subcommand help for detailed usage information.", + }) + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials create [type] [sub command] [options] [args]", + "", + " This command allows create operations on Boundary credential resources. Example:", + "", + " Create a credential:", + "", + ` $ boundary credentials create username-password -credential-store-id cs_1234567890 -username user -password pass`, + "", + " Please see the typed subcommand help for detailed usage information.", + }) + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials update [type] [sub command] [options] [args]", + "", + " This command allows update operations on Boundary credential resources. Example:", + "", + " Update a user password credential:", + "", + ` $ boundary credentials update username-password -id cred_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 []*credentials.Credential) string { + if len(items) == 0 { + return "No credentials found" + } + + var output []string + output = []string{ + "", + "Credential 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().(*credentials.Credential) + 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 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{ + "username": "Username", + "password_hmac": "Password HMAC", +} diff --git a/internal/cmd/commands/credentialscmd/username_password_credentials.gen.go b/internal/cmd/commands/credentialscmd/username_password_credentials.gen.go new file mode 100644 index 000000000..a9c7833d7 --- /dev/null +++ b/internal/cmd/commands/credentialscmd/username_password_credentials.gen.go @@ -0,0 +1,258 @@ +// Code generated by "make cli"; DO NOT EDIT. +package credentialscmd + +import ( + "errors" + "fmt" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/internal/cmd/base" + "github.com/hashicorp/boundary/internal/cmd/common" + "github.com/hashicorp/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initUsernamePasswordFlags() { + flagsOnce.Do(func() { + extraFlags := extraUsernamePasswordActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsUsernamePasswordMap[k] = append(flagsUsernamePasswordMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*UsernamePasswordCommand)(nil) + _ cli.CommandAutocomplete = (*UsernamePasswordCommand)(nil) +) + +type UsernamePasswordCommand struct { + *base.Command + + Func string + + plural string + + extraUsernamePasswordCmdVars +} + +func (c *UsernamePasswordCommand) AutocompleteArgs() complete.Predictor { + initUsernamePasswordFlags() + return complete.PredictAnything +} + +func (c *UsernamePasswordCommand) AutocompleteFlags() complete.Flags { + initUsernamePasswordFlags() + return c.Flags().Completions() +} + +func (c *UsernamePasswordCommand) Synopsis() string { + if extra := extraUsernamePasswordSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential" + + synopsisStr = fmt.Sprintf("%s %s", "username_password-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *UsernamePasswordCommand) Help() string { + initUsernamePasswordFlags() + + var helpStr string + helpMap := common.HelpMap("credential") + + switch c.Func { + + default: + + helpStr = c.extraUsernamePasswordHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsUsernamePasswordMap = map[string][]string{ + + "create": {"credential-store-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *UsernamePasswordCommand) Flags() *base.FlagSets { + if len(flagsUsernamePasswordMap[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, "username_password-type credential", flagsUsernamePasswordMap, c.Func) + + extraUsernamePasswordFlagsFunc(c, set, f) + + return set +} + +func (c *UsernamePasswordCommand) Run(args []string) int { + initUsernamePasswordFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + } + + c.plural = "username_password-type credential" + switch c.Func { + case "list": + c.plural = "username_password-type credentials" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsUsernamePasswordMap[c.Func], "id") && c.FlagId == "" { + c.PrintCliError(errors.New("ID is required but not passed in via -id")) + return base.CommandUserError + } + + var opts []credentials.Option + + if strutil.StrListContains(flagsUsernamePasswordMap[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 c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + credentialsClient := credentials.NewClient(client) + + switch c.FlagName { + case "": + case "null": + opts = append(opts, credentials.DefaultName()) + default: + opts = append(opts, credentials.WithName(c.FlagName)) + } + + switch c.FlagDescription { + case "": + case "null": + opts = append(opts, credentials.DefaultDescription()) + default: + opts = append(opts, credentials.WithDescription(c.FlagDescription)) + } + + if c.FlagFilter != "" { + opts = append(opts, credentials.WithFilter(c.FlagFilter)) + } + + var version uint32 + + switch c.Func { + + case "update": + switch c.FlagVersion { + case 0: + opts = append(opts, credentials.WithAutomaticVersioning(true)) + default: + version = uint32(c.FlagVersion) + } + + } + + if ok := extraUsernamePasswordFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = credentialsClient.Create(c.Context, "username_password", c.FlagCredentialStoreId, opts...) + + case "update": + result, err = credentialsClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraUsernamePasswordActions(c, result, err, credentialsClient, version, opts) + + if err != nil { + if apiErr := api.AsServerError(err); apiErr != nil { + var opts []base.Option + + opts = append(opts, base.WithAttributeFieldPrefix("username_password")) + + 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 := printCustomUsernamePasswordActionOutput(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 ( + extraUsernamePasswordActionsFlagsMapFunc = func() map[string][]string { return nil } + extraUsernamePasswordSynopsisFunc = func(*UsernamePasswordCommand) string { return "" } + extraUsernamePasswordFlagsFunc = func(*UsernamePasswordCommand, *base.FlagSets, *base.FlagSet) {} + extraUsernamePasswordFlagsHandlingFunc = func(*UsernamePasswordCommand, *base.FlagSets, *[]credentials.Option) bool { return true } + executeExtraUsernamePasswordActions = func(_ *UsernamePasswordCommand, inResult api.GenericResult, inErr error, _ *credentials.Client, _ uint32, _ []credentials.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomUsernamePasswordActionOutput = func(*UsernamePasswordCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialscmd/username_password_funcs.go b/internal/cmd/commands/credentialscmd/username_password_funcs.go new file mode 100644 index 000000000..a950e4a9d --- /dev/null +++ b/internal/cmd/commands/credentialscmd/username_password_funcs.go @@ -0,0 +1,97 @@ +package credentialscmd + +import ( + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func init() { + extraUsernamePasswordFlagsFunc = extraUsernamePasswordFlagsFuncImpl + extraUsernamePasswordActionsFlagsMapFunc = extraUsernamePasswordActionsFlagsMapFuncImpl + extraUsernamePasswordFlagsHandlingFunc = extraUsernamePasswordFlagHandlingFuncImpl +} + +const ( + usernameFlagName = "username" + passwordFlagName = "password" +) + +type extraUsernamePasswordCmdVars struct { + flagUsername string + flagPassword string +} + +func extraUsernamePasswordActionsFlagsMapFuncImpl() map[string][]string { + flags := map[string][]string{ + "create": { + usernameFlagName, + passwordFlagName, + }, + } + flags["update"] = flags["create"] + return flags +} + +func extraUsernamePasswordFlagsFuncImpl(c *UsernamePasswordCommand, set *base.FlagSets, _ *base.FlagSet) { + f := set.NewFlagSet("User Password Credential Options") + + for _, name := range flagsUsernamePasswordMap[c.Func] { + switch name { + case usernameFlagName: + f.StringVar(&base.StringVar{ + Name: usernameFlagName, + Target: &c.flagUsername, + Usage: "The username associated with the credential.", + }) + case passwordFlagName: + f.StringVar(&base.StringVar{ + Name: passwordFlagName, + Target: &c.flagPassword, + Usage: "The password associated with the credential.", + }) + } + } +} + +func extraUsernamePasswordFlagHandlingFuncImpl(c *UsernamePasswordCommand, _ *base.FlagSets, opts *[]credentials.Option) bool { + switch c.flagUsername { + case "": + default: + *opts = append(*opts, credentials.WithUsernamePasswordCredentialUsername(c.flagUsername)) + } + switch c.flagPassword { + case "": + default: + *opts = append(*opts, credentials.WithUsernamePasswordCredentialPassword(c.flagPassword)) + } + + return true +} + +func (c *UsernamePasswordCommand) extraUsernamePasswordHelpFunc(_ map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials create username-password -credential-store-id [options] [args]", + "", + " Create a user password credential. Example:", + "", + ` $ boundary credentials create username-password -credential-store-id csvlt_1234567890 -username user -password pass`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credentials update user password [options] [args]", + "", + " Update a user password credential given its ID. Example:", + "", + ` $ boundary credentials update username-password -id clvlt_1234567890 -name devops -description "For DevOps usage"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/commands/credentialstorescmd/funcs.go b/internal/cmd/commands/credentialstorescmd/funcs.go index 94772e536..a0ed898f4 100644 --- a/internal/cmd/commands/credentialstorescmd/funcs.go +++ b/internal/cmd/commands/credentialstorescmd/funcs.go @@ -33,7 +33,11 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { "", " Create a vault-type credential store:", "", - ` $ boundary credential-stores create vault -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`, + ` $ boundary credential-stores create vault -scope-id p_1234567890 -vault-address "http://localhost:8200" -vault-token "s.s0m3t0k3n"`, + "", + " Create a static-type credential store:", + "", + ` $ boundary credential-stores create static -scope-id p_1234567890`, "", " Please see the typed subcommand help for detailed usage information.", }) @@ -47,6 +51,10 @@ func (c *Command) extraHelpFunc(helpMap map[string]func() string) string { "", ` $ boundary credential-stores update vault -id csvlt_1234567890 -name devops -description "For DevOps usage"`, "", + " Update a static-type credential store:", + "", + ` $ boundary credential-stores update static -id cs_1234567890 -name devops -description "For DevOps usage"`, + "", " Please see the typed subcommand help for detailed usage information.", }) default: diff --git a/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go b/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go new file mode 100644 index 000000000..cb65d9545 --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/static_credentialstores.gen.go @@ -0,0 +1,261 @@ +// 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/go-secure-stdlib/strutil" + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func initStaticFlags() { + flagsOnce.Do(func() { + extraFlags := extraStaticActionsFlagsMapFunc() + for k, v := range extraFlags { + flagsStaticMap[k] = append(flagsStaticMap[k], v...) + } + }) +} + +var ( + _ cli.Command = (*StaticCommand)(nil) + _ cli.CommandAutocomplete = (*StaticCommand)(nil) +) + +type StaticCommand struct { + *base.Command + + Func string + + plural string +} + +func (c *StaticCommand) AutocompleteArgs() complete.Predictor { + initStaticFlags() + return complete.PredictAnything +} + +func (c *StaticCommand) AutocompleteFlags() complete.Flags { + initStaticFlags() + return c.Flags().Completions() +} + +func (c *StaticCommand) Synopsis() string { + if extra := extraStaticSynopsisFunc(c); extra != "" { + return extra + } + + synopsisStr := "credential store" + + synopsisStr = fmt.Sprintf("%s %s", "static-type", synopsisStr) + + return common.SynopsisFunc(c.Func, synopsisStr) +} + +func (c *StaticCommand) Help() string { + initStaticFlags() + + var helpStr string + helpMap := common.HelpMap("credential store") + + switch c.Func { + + default: + + helpStr = c.extraStaticHelpFunc(helpMap) + + } + + // Keep linter from complaining if we don't actually generate code using it + _ = helpMap + return helpStr +} + +var flagsStaticMap = map[string][]string{ + + "create": {"scope-id", "name", "description"}, + + "update": {"id", "name", "description", "version"}, +} + +func (c *StaticCommand) Flags() *base.FlagSets { + if len(flagsStaticMap[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, "static-type credential store", flagsStaticMap, c.Func) + + extraStaticFlagsFunc(c, set, f) + + return set +} + +func (c *StaticCommand) Run(args []string) int { + initStaticFlags() + + switch c.Func { + case "": + return cli.RunResultHelp + + } + + c.plural = "static-type credential store" + switch c.Func { + case "list": + c.plural = "static-type credential stores" + } + + f := c.Flags() + + if err := f.Parse(args); err != nil { + c.PrintCliError(err) + return base.CommandUserError + } + + if strutil.StrListContains(flagsStaticMap[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(flagsStaticMap[c.Func], "scope-id") { + switch c.Func { + + case "create": + if c.FlagScopeId == "" { + c.PrintCliError(errors.New("Scope ID must be passed in via -scope-id or BOUNDARY_SCOPE_ID")) + return base.CommandUserError + } + + } + } + + client, err := c.Client() + if c.WrapperCleanupFunc != nil { + defer func() { + if err := c.WrapperCleanupFunc(); err != nil { + c.PrintCliError(fmt.Errorf("Error cleaning kms wrapper: %w", err)) + } + }() + } + if err != nil { + c.PrintCliError(fmt.Errorf("Error creating API client: %w", err)) + return base.CommandCliError + } + 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 := extraStaticFlagsHandlingFunc(c, f, &opts); !ok { + return base.CommandUserError + } + + var result api.GenericResult + + switch c.Func { + + case "create": + result, err = credentialstoresClient.Create(c.Context, "static", c.FlagScopeId, opts...) + + case "update": + result, err = credentialstoresClient.Update(c.Context, c.FlagId, version, opts...) + + } + + result, err = executeExtraStaticActions(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("static")) + + 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 := printCustomStaticActionOutput(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 ( + extraStaticActionsFlagsMapFunc = func() map[string][]string { return nil } + extraStaticSynopsisFunc = func(*StaticCommand) string { return "" } + extraStaticFlagsFunc = func(*StaticCommand, *base.FlagSets, *base.FlagSet) {} + extraStaticFlagsHandlingFunc = func(*StaticCommand, *base.FlagSets, *[]credentialstores.Option) bool { return true } + executeExtraStaticActions = func(_ *StaticCommand, inResult api.GenericResult, inErr error, _ *credentialstores.Client, _ uint32, _ []credentialstores.Option) (api.GenericResult, error) { + return inResult, inErr + } + printCustomStaticActionOutput = func(*StaticCommand) (bool, error) { return false, nil } +) diff --git a/internal/cmd/commands/credentialstorescmd/static_funcs.go b/internal/cmd/commands/credentialstorescmd/static_funcs.go new file mode 100644 index 000000000..4632de401 --- /dev/null +++ b/internal/cmd/commands/credentialstorescmd/static_funcs.go @@ -0,0 +1,33 @@ +package credentialstorescmd + +import ( + "github.com/hashicorp/boundary/internal/cmd/base" +) + +func (c *StaticCommand) extraStaticHelpFunc(helpMap map[string]func() string) string { + var helpStr string + switch c.Func { + case "create": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores create static [options] [args]", + "", + " Create a static-type credential store. Example:", + "", + ` $ boundary credential-stores create static -scope-id p_1234567890`, + "", + "", + }) + + case "update": + helpStr = base.WrapForHelpText([]string{ + "Usage: boundary credential-stores update static [options] [args]", + "", + " Update a static-type credential store given its ID. Example:", + "", + ` $ boundary credential-stores update static -name devops -description "For DevOps usage"`, + "", + "", + }) + } + return helpStr + c.Flags().Help() +} diff --git a/internal/cmd/gencli/input.go b/internal/cmd/gencli/input.go index 6772eea7d..519613496 100644 --- a/internal/cmd/gencli/input.go +++ b/internal/cmd/gencli/input.go @@ -200,6 +200,21 @@ var inputStructs = map[string][]*cmdInfo{ NeedsSubtypeInCreate: true, PrefixAttributeFieldErrorsWithSubactionPrefix: true, }, + { + ResourceType: resource.CredentialStore.String(), + Pkg: "credentialstores", + StdActions: []string{"create", "update"}, + SubActionPrefix: "static", + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "Scope", + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + PrefixAttributeFieldErrorsWithSubactionPrefix: true, + }, }, "credentiallibraries": { { @@ -227,6 +242,33 @@ var inputStructs = map[string][]*cmdInfo{ PrefixAttributeFieldErrorsWithSubactionPrefix: true, }, }, + "credentials": { + { + ResourceType: resource.Credential.String(), + Pkg: "credentials", + StdActions: []string{"read", "delete", "list"}, + IsAbstractType: true, + HasExtraHelpFunc: true, + Container: "CredentialStore", + HasId: true, + }, + { + ResourceType: resource.Credential.String(), + Pkg: "credentials", + StdActions: []string{"create", "update"}, + SubActionPrefix: "username_password", + HasExtraCommandVars: true, + SkipNormalHelp: true, + HasExtraHelpFunc: true, + HasId: true, + HasName: true, + HasDescription: true, + Container: "CredentialStore", + VersionedActions: []string{"update"}, + NeedsSubtypeInCreate: true, + PrefixAttributeFieldErrorsWithSubactionPrefix: true, + }, + }, "groups": { { ResourceType: resource.Group.String(), diff --git a/internal/credential/credential.go b/internal/credential/credential.go index 33e46cb0b..e085d6c47 100644 --- a/internal/credential/credential.go +++ b/internal/credential/credential.go @@ -86,6 +86,12 @@ type Dynamic interface { Purpose() Purpose } +// Static is a static credential that is stored directly in a credential store. +type Static interface { + boundary.Resource + GetStoreId() string +} + // 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. @@ -118,8 +124,8 @@ type Password string // PrivateKey represents a secret private key. type PrivateKey []byte -// UserPassword is a credential containing a username and a password. -type UserPassword interface { +// UsernamePassword is a credential containing a username and a password. +type UsernamePassword interface { Credential Username() string Password() Password diff --git a/internal/credential/static/credential_store.go b/internal/credential/static/credential_store.go new file mode 100644 index 000000000..e1875cea6 --- /dev/null +++ b/internal/credential/static/credential_store.go @@ -0,0 +1,65 @@ +package static + +import ( + "github.com/hashicorp/boundary/internal/credential/static/store" + "github.com/hashicorp/boundary/internal/oplog" + "google.golang.org/protobuf/proto" +) + +// A CredentialStore contains credentials. It is owned by a scope. +type CredentialStore struct { + *store.CredentialStore + tableName string `gorm:"-"` +} + +// NewCredentialStore creates a new in memory static CredentialStore assigned to scopeId. +// Name and description are the only valid options. All other options are ignored. +func NewCredentialStore(scopeId string, opt ...Option) (*CredentialStore, error) { + opts := getOpts(opt...) + cs := &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: scopeId, + Name: opts.withName, + Description: opts.withDescription, + }, + } + return cs, nil +} + +func allocCredentialStore() *CredentialStore { + return &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + } +} + +func (cs *CredentialStore) clone() *CredentialStore { + cp := proto.Clone(cs.CredentialStore) + return &CredentialStore{ + CredentialStore: cp.(*store.CredentialStore), + } +} + +// TableName returns the table name. +func (cs *CredentialStore) TableName() string { + if cs.tableName != "" { + return cs.tableName + } + return "credential_static_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-static-store"}, + "op-type": []string{op.String()}, + } + if cs.ScopeId != "" { + metadata["scope-id"] = []string{cs.ScopeId} + } + return metadata +} diff --git a/internal/credential/static/credential_store_test.go b/internal/credential/static/credential_store_test.go new file mode 100644 index 000000000..ce68252c1 --- /dev/null +++ b/internal/credential/static/credential_store_test.go @@ -0,0 +1,111 @@ +package static + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential/static/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 + + type args struct { + scopeId string + opts []Option + } + + tests := []struct { + name string + args args + want *CredentialStore + wantCreateErr bool + }{ + { + name: "missing-scope-id", + want: allocCredentialStore(), + wantCreateErr: true, + }, + { + name: "valid-no-options", + args: args{ + scopeId: scope.PublicId, + }, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + }, + }, + }, + { + name: "valid-with-name", + args: args{ + scopeId: scope.PublicId, + opts: []Option{ + WithName("test-name"), + }, + }, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + Name: "test-name", + }, + }, + }, + { + name: "valid-with-description", + args: args{ + scopeId: scope.PublicId, + opts: []Option{ + WithDescription("test-description"), + }, + }, + want: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: scope.PublicId, + Description: "test-description", + }, + }, + }, + } + + 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.opts...) + 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(context.Background()) + assert.NoError(err) + + tt.want.PublicId = id + got.PublicId = id + + err = rw.Create(context.Background(), got) + if tt.wantCreateErr { + require.Error(err) + return + } + + assert.NoError(err) + }) + } +} diff --git a/internal/credential/static/doc.go b/internal/credential/static/doc.go new file mode 100644 index 000000000..d673f57b0 --- /dev/null +++ b/internal/credential/static/doc.go @@ -0,0 +1,2 @@ +// Package static implements a credential store for static credentials. +package static diff --git a/internal/credential/static/fields.go b/internal/credential/static/fields.go new file mode 100644 index 000000000..445ce4ca3 --- /dev/null +++ b/internal/credential/static/fields.go @@ -0,0 +1,9 @@ +package static + +// These constants are the field names used in the static related field masks. +const ( + nameField = "Name" + descriptionField = "Description" + usernameField = "Username" + passwordField = "Password" +) diff --git a/internal/credential/static/options.go b/internal/credential/static/options.go new file mode 100644 index 000000000..04c7a87e4 --- /dev/null +++ b/internal/credential/static/options.go @@ -0,0 +1,47 @@ +package static + +// 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 +} + +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 + } +} diff --git a/internal/credential/static/options_test.go b/internal/credential/static/options_test.go new file mode 100644 index 000000000..1c3d2e0f6 --- /dev/null +++ b/internal/credential/static/options_test.go @@ -0,0 +1,29 @@ +package static + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +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) + }) +} diff --git a/internal/credential/static/public_ids.go b/internal/credential/static/public_ids.go new file mode 100644 index 000000000..fb3730d2e --- /dev/null +++ b/internal/credential/static/public_ids.go @@ -0,0 +1,44 @@ +package static + +import ( + "context" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/types/subtypes" +) + +func init() { + if err := subtypes.Register(credential.Domain, Subtype, CredentialStorePrefix); err != nil { + panic(err) + } + if err := subtypes.Register(credential.Domain, UsernamePasswordSubtype, CredentialPrefix); err != nil { + panic(err) + } +} + +// PublicId prefixes for the resources in the static package. +const ( + CredentialStorePrefix = "cs" + CredentialPrefix = "cred" + + Subtype = subtypes.Subtype("static") + UsernamePasswordSubtype = subtypes.Subtype("username_password") +) + +func newCredentialStoreId(ctx context.Context) (string, error) { + id, err := db.NewPublicId(CredentialStorePrefix) + if err != nil { + return "", errors.Wrap(ctx, err, "static.newCredentialStoreId") + } + return id, nil +} + +func newCredentialId(ctx context.Context) (string, error) { + id, err := db.NewPublicId(CredentialPrefix) + if err != nil { + return "", errors.Wrap(ctx, err, "static.newCredentialId") + } + return id, nil +} diff --git a/internal/credential/static/repository.go b/internal/credential/static/repository.go new file mode 100644 index 000000000..df03971ec --- /dev/null +++ b/internal/credential/static/repository.go @@ -0,0 +1,49 @@ +package static + +import ( + "context" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" +) + +// A Repository stores and retrieves the persistent types in the static +// package. It is not safe to use a repository concurrently. +type Repository struct { + reader db.Reader + writer db.Writer + kms *kms.Kms + // 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(ctx context.Context, r db.Reader, w db.Writer, kms *kms.Kms, opt ...Option) (*Repository, error) { + const op = "static.NewRepository" + switch { + case r == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "db.Reader") + case w == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "db.Writer") + case kms == nil: + return nil, errors.New(ctx, errors.InvalidParameter, op, "kms") + } + + 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, + defaultLimit: opts.withLimit, + }, nil +} diff --git a/internal/credential/static/repository_credential.go b/internal/credential/static/repository_credential.go new file mode 100644 index 000000000..3829397c2 --- /dev/null +++ b/internal/credential/static/repository_credential.go @@ -0,0 +1,309 @@ +package static + +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" +) + +// CreateUsernamePasswordCredential inserts c into the repository and returns a new +// UsernamePasswordCredential containing the credential's PublicId. c is not +// changed. c must not contain a PublicId. The PublicId is generated and +// assigned by this method. c must contain a valid StoreId. +// +// The password is encrypted and a HmacSha256 of the password is calculated. Only the +// PasswordHmac is returned, the plain-text and encrypted password is not returned. +// +// Both c.Name and c.Description are optional. If c.Name is set, it must +// be unique within c.ScopeId. Both c.CreateTime and c.UpdateTime are +// ignored. +func (r *Repository) CreateUsernamePasswordCredential( + ctx context.Context, + scopeId string, + c *UsernamePasswordCredential, + _ ...Option, +) (*UsernamePasswordCredential, error) { + const op = "static.(Repository).CreateUsernamePasswordCredential" + if c == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential") + } + if c.UsernamePasswordCredential == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing embedded credential") + } + if scopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if c.Username == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing username") + } + if c.Password == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing password") + } + if c.StoreId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing store id") + } + if c.PublicId != "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + + c = c.clone() + id, err := newCredentialId(ctx) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + c.PublicId = id + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + // encrypt + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper")) + } + if err := c.encrypt(ctx, databaseWrapper); err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + var newCred *UsernamePasswordCredential + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + newCred = c.clone() + if err := w.Create(ctx, newCred, + db.WithOplog(oplogWrapper, newCred.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + return errors.Wrap(ctx, err, op) + } + + return nil + }, + ) + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s: name %s already exists", c.StoreId, c.Name))) + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in store: %s", c.StoreId))) + } + + // Clear password fields, only passwordHmac should be returned + newCred.CtPassword = nil + newCred.Password = nil + + return newCred, nil +} + +// LookupCredential returns the Credential for the publicId. Returns +// nil, nil if no Credential is found for the publicId. +// TODO: This should hit a view and return the interface type... +func (r *Repository) LookupCredential(ctx context.Context, publicId string, _ ...Option) (*UsernamePasswordCredential, error) { + const op = "static.(Repository).LookupCredential" + if publicId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "no public id") + } + cred := allocUsernamePasswordCredential() + cred.PublicId = publicId + if err := r.reader.LookupByPublicId(ctx, cred); err != nil { + if errors.IsNotFoundError(err) { + return nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) + } + + // Clear password fields, only passwordHmac should be returned + cred.CtPassword = nil + cred.Password = nil + + return cred, nil +} + +// UpdateUsernamePasswordCredential updates the repository entry for c.PublicId with +// the values in c for the fields listed in fieldMaskPaths. It returns a +// new UsernamePasswordCredential containing the updated values and a count of the +// number of records updated. c is not changed. +// +// c must contain a valid PublicId. Only Name, Description, Username and Password can be +// changed. If c.Name is set to a non-empty string, it must be unique within c.ScopeId. +// +// An attribute of c will be set to NULL in the database if the attribute +// in c is the zero value and it is included in fieldMaskPaths. +func (r *Repository) UpdateUsernamePasswordCredential(ctx context.Context, + scopeId string, + c *UsernamePasswordCredential, + version uint32, + fieldMaskPaths []string, + _ ...Option, +) (*UsernamePasswordCredential, int, error) { + const op = "static.(Repository).UpdateUsernamePasswordCredential" + if c == nil { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing Credential") + } + if c.UsernamePasswordCredential == nil { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded Credential") + } + if c.PublicId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") + } + if version == 0 { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } + if scopeId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if c.StoreId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing store id") + } + c = c.clone() + + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold(nameField, f): + case strings.EqualFold(descriptionField, f): + case strings.EqualFold(usernameField, f): + case strings.EqualFold(passwordField, f): + default: + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f) + } + } + dbMask, nullFields := dbcommon.BuildUpdatePaths( + map[string]interface{}{ + nameField: c.Name, + descriptionField: c.Description, + usernameField: c.Username, + passwordField: c.Password, + }, + fieldMaskPaths, + nil, + ) + if len(dbMask) == 0 && len(nullFields) == 0 { + return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask") + } + + for _, f := range fieldMaskPaths { + if strings.EqualFold(passwordField, f) { + // Password has been updated, re-encrypt and recalculate hmac + databaseWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + if err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get database wrapper")) + } + if err := c.encrypt(ctx, databaseWrapper); err != nil { + return nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + + // Set PasswordHmac and CtPassword masks for update. + dbMask = append(dbMask, "PasswordHmac", "CtPassword") + } + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, + errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsUpdated int + var returnedCredential *UsernamePasswordCredential + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + returnedCredential = c.clone() + var err error + rowsUpdated, err = w.Update(ctx, returnedCredential, + dbMask, nullFields, + db.WithOplog(oplogWrapper, returnedCredential.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithVersion(&version)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsUpdated > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") + } + return nil + }, + ) + if err != nil { + return nil, db.NoRowsAffected, err + } + + // Clear password fields, only passwordHmac should be returned + returnedCredential.CtPassword = nil + returnedCredential.Password = nil + + return returnedCredential, rowsUpdated, nil +} + +// ListCredentials returns a slice of UsernamePasswordCredentials for the +// scopeIds. WithLimit is the only option supported. +// TODO: This should hit a view and return the interface type... +func (r *Repository) ListCredentials(ctx context.Context, storeId string, opt ...Option) ([]*UsernamePasswordCredential, error) { + const op = "static.(Repository).ListCredentials" + if storeId == "" { + return nil, errors.New(ctx, 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 creds []*UsernamePasswordCredential + err := r.reader.SearchWhere(ctx, &creds, "store_id = ?", []interface{}{storeId}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + for _, c := range creds { + // Clear password fields, only passwordHmac should be returned + c.CtPassword = nil + c.Password = nil + } + + return creds, nil +} + +// DeleteCredential deletes publicId from the repository and returns +// the number of records deleted. All options are ignored. +// TODO: This should hit a view... +func (r *Repository) DeleteCredential(ctx context.Context, scopeId, id string, _ ...Option) (int, error) { + const op = "static.(Repository).DeleteCredential" + if id == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") + } + if scopeId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + + c := allocUsernamePasswordCredential() + c.PublicId = id + + oplogWrapper, err := r.kms.GetWrapper(ctx, scopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), 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) { + rowsDeleted, err = w.Delete(ctx, c, db.WithOplog(oplogWrapper, c.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsDeleted > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted") + } + return nil + }, + ) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(id)) + } + + return rowsDeleted, nil +} diff --git a/internal/credential/static/repository_credential_store.go b/internal/credential/static/repository_credential_store.go new file mode 100644 index 000000000..61848e7b0 --- /dev/null +++ b/internal/credential/static/repository_credential_store.go @@ -0,0 +1,237 @@ +package static + +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" +) + +// 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. +// +// 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. +func (r *Repository) CreateCredentialStore(ctx context.Context, cs *CredentialStore, _ ...Option) (*CredentialStore, error) { + const op = "static.(Repository).CreateCredentialStore" + if cs == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing CredentialStore") + } + if cs.CredentialStore == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing embedded CredentialStore") + } + if cs.ScopeId == "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if cs.PublicId != "" { + return nil, errors.New(ctx, errors.InvalidParameter, op, "public id not empty") + } + + cs = cs.clone() + id, err := newCredentialStoreId(ctx) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cs.PublicId = id + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var newCredentialStore *CredentialStore + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + newCredentialStore = cs.clone() + if err := w.Create(ctx, newCredentialStore, + db.WithOplog(oplogWrapper, newCredentialStore.oplog(oplog.OpType_OP_TYPE_CREATE))); err != nil { + return errors.Wrap(ctx, err, op) + } + + return nil + }, + ) + if err != nil { + if errors.IsUniqueError(err) { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s: name %s already exists", cs.ScopeId, cs.Name))) + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("in scope: %s", cs.ScopeId))) + } + + return newCredentialStore, 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 = "static.(Repository).LookupCredentialStore" + if publicId == "" { + return nil, errors.New(ctx, 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 nil, nil + } + return nil, errors.Wrap(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for: %s", publicId))) + } + return cs, nil +} + +// 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 and Description can be changed. If cs.Name +// is set to a non-empty string, it must be unique within cs.ScopeId. +// +// 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 = "static.(Repository).UpdateCredentialStore" + if cs == nil { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing CredentialStore") + } + if cs.CredentialStore == nil { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing embedded CredentialStore") + } + if cs.PublicId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing public id") + } + if version == 0 { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") + } + if cs.ScopeId == "" { + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + cs = cs.clone() + + for _, f := range fieldMaskPaths { + switch { + case strings.EqualFold(nameField, f): + case strings.EqualFold(descriptionField, f): + default: + return nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidFieldMask, op, f) + } + } + dbMask, nullFields := dbcommon.BuildUpdatePaths( + map[string]interface{}{ + nameField: cs.Name, + descriptionField: cs.Description, + }, + fieldMaskPaths, + nil, + ) + if len(dbMask) == 0 && len(nullFields) == 0 { + return nil, db.NoRowsAffected, errors.New(ctx, errors.EmptyFieldMask, op, "missing field mask") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return nil, db.NoRowsAffected, + errors.Wrap(ctx, err, op, errors.WithMsg("unable to get oplog wrapper")) + } + + var rowsUpdated int + var returnedCredentialStore *CredentialStore + _, err = r.writer.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, w db.Writer) error { + returnedCredentialStore = cs.clone() + var err error + rowsUpdated, err = w.Update(ctx, returnedCredentialStore, + dbMask, nullFields, + db.WithOplog(oplogWrapper, returnedCredentialStore.oplog(oplog.OpType_OP_TYPE_UPDATE)), + db.WithVersion(&version)) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsUpdated > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been updated") + } + return nil + }, + ) + if err != nil { + return nil, db.NoRowsAffected, err + } + + 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 = "static.(Repository).ListCredentialStores" + if len(scopeIds) == 0 { + return nil, errors.New(ctx, 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 []*CredentialStore + err := r.reader.SearchWhere(ctx, &credentialStores, "scope_id in (?)", []interface{}{scopeIds}, db.WithLimit(limit)) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + return credentialStores, 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 = "static.(Repository).DeleteCredentialStore" + if publicId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidPublicId, op, "missing 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(ctx, err, op, errors.WithMsg(fmt.Sprintf("failed for %s", publicId))) + } + if cs.ScopeId == "" { + return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "no scope id") + } + + oplogWrapper, err := r.kms.GetWrapper(ctx, cs.ScopeId, kms.KeyPurposeOplog) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), 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) { + rowsDeleted, err = w.Delete(ctx, cs, db.WithOplog(oplogWrapper, cs.oplog(oplog.OpType_OP_TYPE_DELETE))) + if err != nil { + return errors.Wrap(ctx, err, op) + } + if rowsDeleted > 1 { + return errors.New(ctx, errors.MultipleRecords, op, "more than 1 resource would have been deleted") + } + return nil + }, + ) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg(publicId)) + } + + return rowsDeleted, nil +} diff --git a/internal/credential/static/repository_credential_store_test.go b/internal/credential/static/repository_credential_store_test.go new file mode 100644 index 000000000..72c193ceb --- /dev/null +++ b/internal/credential/static/repository_credential_store_test.go @@ -0,0 +1,746 @@ +package static + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/credential/static/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/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +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_CreateCredentialStore(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)) + + tests := []struct { + name string + store *CredentialStore + wantErr bool + wantErrCode errors.Code + }{ + { + name: "missing-store", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-embedded-store", + store: &CredentialStore{}, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-scope-id", + store: &CredentialStore{ + CredentialStore: &store.CredentialStore{}, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "public-id-set", + store: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: prj.PublicId, + PublicId: "bad-dont-set-this", + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid", + store: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: prj.PublicId, + }, + }, + }, + { + name: "valid-with-name", + store: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: prj.PublicId, + Name: "test-store", + }, + }, + }, + { + name: "valid-with-description", + store: &CredentialStore{ + CredentialStore: &store.CredentialStore{ + ScopeId: prj.PublicId, + Description: "test-store-description", + }, + }, + }, + } + 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(ctx, rw, rw, kms) + require.NoError(err) + require.NotNil(repo) + + got, err := repo.CreateCredentialStore(ctx, tt.store) + if tt.wantErr { + 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.store.PublicId) + require.NotNil(got) + assertPublicId(t, CredentialStorePrefix, got.PublicId) + assert.NotSame(tt.store, 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))) + }) + } + + t.Run("duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(ctx, rw, rw, kms) + require.NoError(err) + require.NotNil(repo) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(err) + + in, err := NewCredentialStore(prj.GetPublicId(), WithName("my-name"), WithDescription("desc")) + assert.NoError(err) + + got, err := repo.CreateCredentialStore(ctx, in) + require.NoError(err) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + + in2, err := NewCredentialStore(prj.GetPublicId(), WithName("my-name"), WithDescription("desc")) + got2, err := repo.CreateCredentialStore(ctx, in2) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + + // Creating credential in different scope should not conflict + in3, err := NewCredentialStore(org.GetPublicId(), WithName("my-name"), WithDescription("desc")) + got3, err := repo.CreateCredentialStore(ctx, in3) + require.NoError(err) + assert.Equal(in.Name, got3.Name) + assert.Equal(in3.Name, got3.Name) + assert.Equal(in3.Description, got3.Description) + + assert.NotEqual(got.PublicId, got3.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)) + store := TestCredentialStore(t, conn, wrapper, prj.PublicId) + + tests := []struct { + name string + id string + want *CredentialStore + wantErr errors.Code + }{ + { + name: "valid-with-client-cert", + id: store.GetPublicId(), + want: store, + }, + { + name: "empty-public-id", + id: "", + wantErr: errors.InvalidParameter, + }, + { + name: "not-found", + id: "cs_fake", + }, + } + + 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(ctx, rw, rw, kms) + 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.Equal(got, tt.want) + }) + } +} + +func TestRepository_UpdateCredentialStore(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + 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, + }, + } + + 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(ctx, rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + tt.orig.ScopeId = prj.GetPublicId() + + 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) + underlyingDB, err := conn.SqlDB(ctx) + require.NoError(err) + dbassert := dbassert.New(t, underlyingDB) + 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.wantCount > 0 { + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, 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) + repo, err := NewRepository(ctx, rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + require.NoError(err) + + 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) + repo, err := NewRepository(ctx, rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + in := &CredentialStore{ + CredentialStore: &store.CredentialStore{ + Name: "test-name-repo", + }, + } + in2 := in.clone() + + 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) + repo, err := NewRepository(ctx, rw, rw, kms) + 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_ListCredentialStores(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) + repo, err := NewRepository(context.Background(), rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + + const num = 10 + var prjs []string + var total int + for i := 0; i < num; i++ { + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + prjs = append(prjs, prj.GetPublicId()) + TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), num) + total += num + } + + type args struct { + scopeIds []string + opt []Option + } + tests := []struct { + name string + args args + wantCnt int + }{ + { + name: "no-limit", + args: args{ + scopeIds: prjs, + opt: []Option{WithLimit(-1)}, + }, + wantCnt: total, + }, + { + name: "default-limit", + args: args{ + scopeIds: prjs, + }, + wantCnt: total, + }, + { + name: "custom-limit", + args: args{ + scopeIds: prjs, + opt: []Option{WithLimit(3)}, + }, + wantCnt: 3, + }, + { + name: "bad-scope", + args: args{ + scopeIds: []string{"bad-id"}, + }, + wantCnt: 0, + }, + } + for _, tt := range tests { + got, err := repo.ListCredentialStores(context.Background(), tt.args.scopeIds, tt.args.opt...) + require.NoError(err) + assert.Equal(tt.wantCnt, len(got)) + } +} + +func TestRepository_DeleteCredentialStore(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + kms := kms.TestKms(t, conn, wrapper) + iam.TestRepo(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + store := TestCredentialStore(t, conn, wrapper, prj.PublicId) + + tests := []struct { + name string + in string + want int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "With no public id", + wantErr: true, + wantErrCode: errors.InvalidPublicId, + }, + { + name: "With non existing account id", + in: "cs_fakeid", + want: 0, + }, + { + name: "With existing account id", + in: store.GetPublicId(), + want: 1, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(context.Background(), rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.DeleteCredentialStore(context.Background(), tt.in) + if tt.wantErr { + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/credential/static/repository_credential_test.go b/internal/credential/static/repository_credential_test.go new file mode 100644 index 000000000..fe1227a1b --- /dev/null +++ b/internal/credential/static/repository_credential_test.go @@ -0,0 +1,896 @@ +package static + +import ( + "context" + "testing" + "time" + + "github.com/hashicorp/boundary/internal/credential/static/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/libs/crypto" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_CreateUsernamePasswordCredential(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 := TestCredentialStore(t, conn, wrapper, prj.PublicId) + + tests := []struct { + name string + scopeId string + cred *UsernamePasswordCredential + wantErr bool + wantErrCode errors.Code + }{ + { + name: "missing-store", + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-embedded-cred", + cred: &UsernamePasswordCredential{}, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-scope-id", + cred: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "my-user", + Password: []byte("secret"), + StoreId: cs.PublicId, + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-username", + scopeId: prj.PublicId, + cred: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Password: []byte("secret"), + StoreId: cs.PublicId, + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-password", + scopeId: prj.PublicId, + cred: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "my-user", + StoreId: cs.PublicId, + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "missing-store-id", + scopeId: prj.PublicId, + cred: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "my-user", + Password: []byte("secret"), + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "valid", + scopeId: prj.PublicId, + cred: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "my-user", + Password: []byte("secret"), + StoreId: cs.PublicId, + }, + }, + }, + } + 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() + kkms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(ctx, rw, rw, kkms) + require.NoError(err) + require.NotNil(repo) + + got, err := repo.CreateUsernamePasswordCredential(ctx, tt.scopeId, tt.cred) + if tt.wantErr { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + assertPublicId(t, CredentialPrefix, got.PublicId) + assert.Equal(tt.cred.Username, got.Username) + assert.Nil(got.Password) + assert.Nil(got.CtPassword) + + // Validate password + lookupCred := allocUsernamePasswordCredential() + lookupCred.PublicId = got.PublicId + require.NoError(rw.LookupById(ctx, lookupCred)) + + databaseWrapper, err := kkms.GetWrapper(context.Background(), tt.scopeId, kms.KeyPurposeDatabase) + require.NoError(err) + require.NoError(lookupCred.decrypt(ctx, databaseWrapper)) + assert.Equal(tt.cred.Password, lookupCred.Password) + + assert.Empty(got.Password) + assert.Empty(got.CtPassword) + assert.NotEmpty(got.PasswordHmac) + + // Validate hmac + hm, err := crypto.HmacSha256(ctx, tt.cred.Password, databaseWrapper, []byte(tt.cred.StoreId), nil, crypto.WithEd25519()) + require.NoError(err) + assert.Equal([]byte(hm), got.PasswordHmac) + + // Validate oplog + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))) + }) + } + + t.Run("duplicate-names", func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + ctx := context.Background() + kms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(ctx, rw, rw, kms) + require.NoError(err) + require.NotNil(repo) + org, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NoError(err) + + prjCs := TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + orgCs := TestCredentialStore(t, conn, wrapper, org.GetPublicId()) + + in, err := NewUsernamePasswordCredential(prjCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("original")) + assert.NoError(err) + + got, err := repo.CreateUsernamePasswordCredential(ctx, prj.PublicId, in) + require.NoError(err) + assert.Equal(in.Name, got.Name) + assert.Equal(in.Description, got.Description) + + in2, err := NewUsernamePasswordCredential(prjCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different")) + got2, err := repo.CreateUsernamePasswordCredential(ctx, prj.GetPublicId(), in2) + assert.Truef(errors.Match(errors.T(errors.NotUnique), err), "want err code: %v got err: %v", errors.NotUnique, err) + assert.Nil(got2) + + // Creating credential in different scope should not conflict + in3, err := NewUsernamePasswordCredential(orgCs.GetPublicId(), "user", "pass", WithName("my-name"), WithDescription("different")) + got3, err := repo.CreateUsernamePasswordCredential(ctx, org.GetPublicId(), in3) + require.NoError(err) + assert.Equal(in3.Name, got3.Name) + assert.Equal(in3.Description, got3.Description) + + assert.NotEqual(got.PublicId, got3.PublicId) + }) +} + +func TestRepository_LookupCredential(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)) + store := TestCredentialStore(t, conn, wrapper, prj.PublicId) + cred := TestUsernamePasswordCredential(t, conn, wrapper, "username", "password", store.PublicId, prj.PublicId) + + tests := []struct { + name string + id string + want *UsernamePasswordCredential + wantErr errors.Code + }{ + { + name: "valid-with-client-cert", + id: cred.GetPublicId(), + want: cred, + }, + { + name: "empty-public-id", + id: "", + wantErr: errors.InvalidParameter, + }, + { + name: "not-found", + id: "cred_fake", + }, + } + + 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(ctx, rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + + got, err := repo.LookupCredential(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.Empty(got.Password) + assert.Empty(got.CtPassword) + assert.NotEmpty(got.PasswordHmac) + }) + } +} + +func TestRepository_ListCredentials(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) + repo, err := NewRepository(context.Background(), rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + + total := 10 + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), total) + + type args struct { + storeId string + opt []Option + } + tests := []struct { + name string + args args + wantCnt int + }{ + { + name: "no-limit", + args: args{ + storeId: store.PublicId, + opt: []Option{WithLimit(-1)}, + }, + wantCnt: total, + }, + { + name: "default-limit", + args: args{ + storeId: store.PublicId, + }, + wantCnt: total, + }, + { + name: "custom-limit", + args: args{ + storeId: store.PublicId, + opt: []Option{WithLimit(3)}, + }, + wantCnt: 3, + }, + { + name: "bad-store", + args: args{ + storeId: "bad-id", + }, + wantCnt: 0, + }, + } + for _, tt := range tests { + got, err := repo.ListCredentials(context.Background(), tt.args.storeId, tt.args.opt...) + require.NoError(err) + assert.Equal(tt.wantCnt, len(got)) + + // Validate only passwordHmac is returned + for _, c := range got { + assert.Empty(c.Password) + assert.Empty(c.CtPassword) + assert.NotEmpty(c.PasswordHmac) + } + } +} + +func TestRepository_DeleteCredential(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + kms := kms.TestKms(t, conn, wrapper) + iam.TestRepo(t, conn, wrapper) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + store := TestCredentialStore(t, conn, wrapper, prj.PublicId) + cred := TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId()) + + tests := []struct { + name string + in string + want int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "With no public id", + wantErr: true, + wantErrCode: errors.InvalidPublicId, + }, + { + name: "With non existing account id", + in: "cred_fakeid", + want: 0, + }, + { + name: "With existing id", + in: cred.GetPublicId(), + want: 1, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + repo, err := NewRepository(context.Background(), rw, rw, kms) + assert.NoError(err) + require.NotNil(repo) + got, err := repo.DeleteCredential(context.Background(), prj.GetPublicId(), tt.in) + if tt.wantErr { + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "Unexpected error %s", err) + return + } + require.NoError(err) + assert.EqualValues(tt.want, got) + }) + } +} + +func TestRepository_UpdateUsernamePasswordCredential(t *testing.T) { + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + + changeName := func(n string) func(credential *UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.Name = n + return c + } + } + + changeDescription := func(d string) func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.Description = d + return c + } + } + + makeNil := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(_ *UsernamePasswordCredential) *UsernamePasswordCredential { + return nil + } + } + + makeEmbeddedNil := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(_ *UsernamePasswordCredential) *UsernamePasswordCredential { + return &UsernamePasswordCredential{} + } + } + + deletePublicId := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.PublicId = "" + return c + } + } + + deleteStoreId := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.StoreId = "" + return c + } + } + + deleteVersion := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.Version = 0 + return c + } + } + + nonExistentPublicId := func() func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.PublicId = "abcd_OOOOOOOOOO" + return c + } + } + + changeUser := func(n string) func(credential *UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.Username = n + return c + } + } + + changePassword := func(d string) func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + c.Password = []byte(d) + return c + } + } + + combine := func(fns ...func(cs *UsernamePasswordCredential) *UsernamePasswordCredential) func(*UsernamePasswordCredential) *UsernamePasswordCredential { + return func(c *UsernamePasswordCredential) *UsernamePasswordCredential { + for _, fn := range fns { + c = fn(c) + } + return c + } + } + + tests := []struct { + name string + orig *UsernamePasswordCredential + chgFn func(*UsernamePasswordCredential) *UsernamePasswordCredential + masks []string + want *UsernamePasswordCredential + wantCount int + wantErr errors.Code + }{ + { + name: "nil-credential", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: makeNil(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "nil-embedded-credential", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: makeEmbeddedNil(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "no-public-id", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: deletePublicId(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidPublicId, + }, + { + name: "no-store-id", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: deleteStoreId(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "no-version", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: deleteVersion(), + masks: []string{"Name", "Description"}, + wantErr: errors.InvalidParameter, + }, + { + name: "updating-non-existent-credential", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: combine(nonExistentPublicId(), changeName("test-update-name-repo")), + masks: []string{"Name"}, + wantErr: errors.RecordNotFound, + }, + { + name: "empty-field-mask", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeName("test-update-name-repo"), + wantErr: errors.EmptyFieldMask, + }, + { + name: "read-only-fields-in-field-mask", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"PublicId", "CreateTime", "UpdateTime", "ScopeId"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "unknown-field-in-field-mask", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"Bilbo"}, + wantErr: errors.InvalidFieldMask, + }, + { + name: "change-name", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeName("test-update-name-repo"), + masks: []string{"Name"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-update-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "change-description", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeDescription("test-update-description-repo"), + masks: []string{"Description"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Description: "test-update-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "change-name-and-description", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("test-update-name-repo")), + masks: []string{"Name", "Description"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-update-name-repo", + Description: "test-update-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "change-username", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changeUser("test-update-user"), + masks: []string{"Username"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-update-user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "change-password", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: changePassword("test-update-pass"), + masks: []string{"Password"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("test-update-pass"), + }, + }, + wantCount: 1, + }, + { + name: "change-username-and-password", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + chgFn: combine(changeUser("test-update-user"), changePassword("test-update-pass")), + masks: []string{"Username", "Password"}, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-update-user", + Password: []byte("test-update-pass"), + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-password", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Username"}, + chgFn: combine(changeUser("test-new-user"), changePassword("")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-new-user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-username", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Password"}, + chgFn: combine(changeUser(""), changePassword("test-new-password")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "user", + Password: []byte("test-new-password"), + }, + }, + wantCount: 1, + }, + { + name: "delete-name", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Name"}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "delete-description", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Description"}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-name", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Description"}, + chgFn: combine(changeDescription("test-update-description-repo"), changeName("")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-update-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + wantCount: 1, + }, + { + name: "do-not-delete-description", + orig: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + masks: []string{"Name"}, + chgFn: combine(changeDescription(""), changeName("test-update-name-repo")), + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Name: "test-update-name-repo", + Description: "test-description-repo", + Username: "user", + Password: []byte("pass"), + }, + }, + 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() + kkms := kms.TestKms(t, conn, wrapper) + repo, err := NewRepository(ctx, rw, rw, kkms) + assert.NoError(err) + require.NotNil(repo) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + tt.orig.StoreId = store.PublicId + + orig, err := repo.CreateUsernamePasswordCredential(ctx, prj.GetPublicId(), tt.orig) + assert.NoError(err) + require.NotNil(orig) + + if tt.chgFn != nil { + orig = tt.chgFn(orig) + } + var version uint32 + if orig != nil { + version = orig.GetVersion() + } + got, gotCount, err := repo.UpdateUsernamePasswordCredential(ctx, prj.GetPublicId(), orig, version, 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, CredentialPrefix, got.PublicId) + assert.Equal(tt.wantCount, gotCount, "row count") + assert.NotSame(tt.orig, got) + assert.Equal(tt.orig.StoreId, got.StoreId) + underlyingDB, err := conn.SqlDB(ctx) + require.NoError(err) + dbassert := dbassert.New(t, underlyingDB) + if tt.want.Name == "" { + got := got.clone() + dbassert.IsNull(got, "name") + } else { + assert.Equal(tt.want.Name, got.Name) + } + + if tt.want.Description == "" { + got := got.clone() + dbassert.IsNull(got, "description") + } else { + assert.Equal(tt.want.Description, got.Description) + } + + assert.Equal(tt.want.Username, got.Username) + + // Validate only passwordHmac is returned + assert.Empty(got.Password) + assert.Empty(got.CtPassword) + assert.NotEmpty(got.PasswordHmac) + + // Validate hmac + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase) + require.NoError(err) + hm, err := crypto.HmacSha256(ctx, tt.want.Password, databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(err) + assert.Equal([]byte(hm), got.PasswordHmac) + + if tt.wantCount > 0 { + assert.NoError(db.TestVerifyOplog(t, rw, got.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))) + } + }) + } +} diff --git a/internal/credential/static/repository_test.go b/internal/credential/static/repository_test.go new file mode 100644 index 000000000..390c829e5 --- /dev/null +++ b/internal/credential/static/repository_test.go @@ -0,0 +1,119 @@ +package static + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/kms" + "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) + + type args struct { + r db.Reader + w db.Writer + kms *kms.Kms + opts []Option + } + + tests := []struct { + name string + args args + want *Repository + wantIsErr errors.Code + }{ + { + name: "valid", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: db.DefaultLimit, + }, + }, + { + name: "valid-with-limit", + args: args{ + r: rw, + w: rw, + kms: kmsCache, + opts: []Option{WithLimit(5)}, + }, + want: &Repository{ + reader: rw, + writer: rw, + kms: kmsCache, + defaultLimit: 5, + }, + }, + { + name: "nil-reader", + args: args{ + r: nil, + w: rw, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-writer", + args: args{ + r: rw, + w: nil, + kms: kmsCache, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "nil-kms", + args: args{ + r: rw, + w: rw, + kms: nil, + }, + want: nil, + wantIsErr: errors.InvalidParameter, + }, + { + name: "all-nils", + args: args{ + r: nil, + w: nil, + kms: 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(context.Background(), tt.args.r, tt.args.w, tt.args.kms, 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/static/store/static.pb.go b/internal/credential/static/store/static.pb.go new file mode 100644 index 000000000..3ba2f1032 --- /dev/null +++ b/internal/credential/static/store/static.pb.go @@ -0,0 +1,467 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc (unknown) +// source: controller/storage/credential/static/store/v1/static.proto + +// Package store provides protobufs for storing types in the static +// credential package. + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/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"` + // 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"` + // The scope_id of the owning scope. + // It must be set. + // @inject_tag: `gorm:"not_null"` + ScopeId string `protobuf:"bytes,6,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,7,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` +} + +func (x *CredentialStore) Reset() { + *x = CredentialStore{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_static_store_v1_static_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_static_store_v1_static_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_static_store_v1_static_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) 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 +} + +type UsernamePasswordCredential 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 static 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"` + // username is the username associated with the credential. + // It must be set. + // @inject_tag: `gorm:"not_null"` + Username string `protobuf:"bytes,8,opt,name=username,proto3" json:"username,omitempty" gorm:"not_null"` + // password is the plain-text of the password associated with the credential. We are + // not storing this plain-text password in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,password_data"` + Password []byte `protobuf:"bytes,9,opt,name=password,proto3" json:"password,omitempty" gorm:"-" wrapping:"pt,password_data"` + // ct_password is the ciphertext of the password. It + // is stored in the database. + // @inject_tag: `gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"` + CtPassword []byte `protobuf:"bytes,10,opt,name=ct_password,json=ctPassword,proto3" json:"ct_password,omitempty" gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"` + // password_hmac is a sha256-hmac of the unencrypted password. It is recalculated + // everytime the password is updated. + // @inject_tag: `gorm:"not_null"` + PasswordHmac []byte `protobuf:"bytes,11,opt,name=password_hmac,json=passwordHmac,proto3" json:"password_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,12,opt,name=key_id,json=keyId,proto3" json:"key_id,omitempty" gorm:"not_null"` +} + +func (x *UsernamePasswordCredential) Reset() { + *x = UsernamePasswordCredential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_credential_static_store_v1_static_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UsernamePasswordCredential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsernamePasswordCredential) ProtoMessage() {} + +func (x *UsernamePasswordCredential) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_credential_static_store_v1_static_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 UsernamePasswordCredential.ProtoReflect.Descriptor instead. +func (*UsernamePasswordCredential) Descriptor() ([]byte, []int) { + return file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP(), []int{1} +} + +func (x *UsernamePasswordCredential) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *UsernamePasswordCredential) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *UsernamePasswordCredential) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *UsernamePasswordCredential) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *UsernamePasswordCredential) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *UsernamePasswordCredential) GetStoreId() string { + if x != nil { + return x.StoreId + } + return "" +} + +func (x *UsernamePasswordCredential) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *UsernamePasswordCredential) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +func (x *UsernamePasswordCredential) GetPassword() []byte { + if x != nil { + return x.Password + } + return nil +} + +func (x *UsernamePasswordCredential) GetCtPassword() []byte { + if x != nil { + return x.CtPassword + } + return nil +} + +func (x *UsernamePasswordCredential) GetPasswordHmac() []byte { + if x != nil { + return x.PasswordHmac + } + return nil +} + +func (x *UsernamePasswordCredential) GetKeyId() string { + if x != nil { + return x.KeyId + } + return "" +} + +var File_controller_storage_credential_static_store_v1_static_proto protoreflect.FileDescriptor + +var file_controller_storage_credential_static_store_v1_static_proto_rawDesc = []byte{ + 0x0a, 0x3a, 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, 0x61, 0x74, 0x69, 0x63, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, + 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2d, 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, 0x61, 0x74, + 0x69, 0x63, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 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, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, 0x02, 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, 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, + 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x06, 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, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x22, 0xfd, 0x04, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 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, 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, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x08, 0x20, 0x01, 0x28, 0x09, 0x42, 0x23, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x08, 0x55, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, + 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, + 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x23, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, 0x08, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x74, 0x5f, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x74, 0x50, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x51, 0x0a, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x2c, 0xc2, + 0xdd, 0x29, 0x28, 0x0a, 0x0c, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, + 0x63, 0x12, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, + 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x52, 0x0c, 0x70, 0x61, 0x73, + 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, 0x12, 0x15, 0x0a, 0x06, 0x6b, 0x65, 0x79, + 0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6b, 0x65, 0x79, 0x49, 0x64, + 0x42, 0x46, 0x5a, 0x44, 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, 0x61, 0x74, 0x69, 0x63, 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_static_store_v1_static_proto_rawDescOnce sync.Once + file_controller_storage_credential_static_store_v1_static_proto_rawDescData = file_controller_storage_credential_static_store_v1_static_proto_rawDesc +) + +func file_controller_storage_credential_static_store_v1_static_proto_rawDescGZIP() []byte { + file_controller_storage_credential_static_store_v1_static_proto_rawDescOnce.Do(func() { + file_controller_storage_credential_static_store_v1_static_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_credential_static_store_v1_static_proto_rawDescData) + }) + return file_controller_storage_credential_static_store_v1_static_proto_rawDescData +} + +var file_controller_storage_credential_static_store_v1_static_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_controller_storage_credential_static_store_v1_static_proto_goTypes = []interface{}{ + (*CredentialStore)(nil), // 0: controller.storage.credential.static.store.v1.CredentialStore + (*UsernamePasswordCredential)(nil), // 1: controller.storage.credential.static.store.v1.UsernamePasswordCredential + (*timestamp.Timestamp)(nil), // 2: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_credential_static_store_v1_static_proto_depIdxs = []int32{ + 2, // 0: controller.storage.credential.static.store.v1.CredentialStore.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // 1: controller.storage.credential.static.store.v1.CredentialStore.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // 2: controller.storage.credential.static.store.v1.UsernamePasswordCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // 3: controller.storage.credential.static.store.v1.UsernamePasswordCredential.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 4, // [4:4] is the sub-list for method output_type + 4, // [4:4] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name +} + +func init() { file_controller_storage_credential_static_store_v1_static_proto_init() } +func file_controller_storage_credential_static_store_v1_static_proto_init() { + if File_controller_storage_credential_static_store_v1_static_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_credential_static_store_v1_static_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_static_store_v1_static_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UsernamePasswordCredential); 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_static_store_v1_static_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_credential_static_store_v1_static_proto_goTypes, + DependencyIndexes: file_controller_storage_credential_static_store_v1_static_proto_depIdxs, + MessageInfos: file_controller_storage_credential_static_store_v1_static_proto_msgTypes, + }.Build() + File_controller_storage_credential_static_store_v1_static_proto = out.File + file_controller_storage_credential_static_store_v1_static_proto_rawDesc = nil + file_controller_storage_credential_static_store_v1_static_proto_goTypes = nil + file_controller_storage_credential_static_store_v1_static_proto_depIdxs = nil +} diff --git a/internal/credential/static/testing.go b/internal/credential/static/testing.go new file mode 100644 index 000000000..9db96217d --- /dev/null +++ b/internal/credential/static/testing.go @@ -0,0 +1,131 @@ +package static + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestCredentialStore creates a static credential store in the provided DB with +// the provided scope and any values passed in through the Options vars. +// If any errors are encountered during the creation of the store, the test will fail. +func TestCredentialStore(t testing.TB, conn *db.DB, wrapper wrapping.Wrapper, scopeId 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, opts...) + assert.NoError(t, err) + require.NotNil(t, cs) + id, err := newCredentialStoreId(ctx) + 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)) + return nil + }, + ) + require.NoError(t, err2) + + return cs +} + +// TestCredentialStores creates count number of static 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.TB, conn *db.DB, wrapper wrapping.Wrapper, scopeId string, count int) []*CredentialStore { + t.Helper() + ctx := context.Background() + kmsCache := kms.TestKms(t, conn, wrapper) + + databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + assert.NoError(t, err) + require.NotNil(t, databaseWrapper) + + css := make([]*CredentialStore, 0, count) + for i := 0; i < count; i++ { + css = append(css, TestCredentialStore(t, conn, wrapper, scopeId)) + } + return css +} + +// TestUsernamePasswordCredential creates a user password credential in the provided DB with +// the provided scope and any values passed in through. +// If any errors are encountered during the creation of the store, the test will fail. +func TestUsernamePasswordCredential( + t testing.TB, + conn *db.DB, + wrapper wrapping.Wrapper, + username, password, storeId, scopeId string, + opts ...Option, +) *UsernamePasswordCredential { + 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) + + cred, err := NewUsernamePasswordCredential(storeId, username, credential.Password(password), opts...) + require.NoError(t, err) + require.NotNil(t, cred) + + id, err := newCredentialId(ctx) + require.NoError(t, err) + cred.PublicId = id + + err = cred.encrypt(ctx, databaseWrapper) + require.NoError(t, err) + + _, err2 := w.DoTx(ctx, db.StdRetryCnt, db.ExpBackoff{}, + func(_ db.Reader, iw db.Writer) error { + require.NoError(t, iw.Create(ctx, cred)) + return nil + }, + ) + require.NoError(t, err2) + + return cred +} + +// TestUsernamePasswordCredentials creates count number of user password credentials in +// the provided DB with the provided scope id. If any errors are +// encountered during the creation of the credentials, the test will fail. +func TestUsernamePasswordCredentials( + t testing.TB, + conn *db.DB, + wrapper wrapping.Wrapper, + username, password, storeId, scopeId string, + count int, +) []*UsernamePasswordCredential { + t.Helper() + ctx := context.Background() + kmsCache := kms.TestKms(t, conn, wrapper) + + databaseWrapper, err := kmsCache.GetWrapper(ctx, scopeId, kms.KeyPurposeDatabase) + assert.NoError(t, err) + require.NotNil(t, databaseWrapper) + + creds := make([]*UsernamePasswordCredential, 0, count) + for i := 0; i < count; i++ { + creds = append(creds, TestUsernamePasswordCredential(t, conn, wrapper, username, password, storeId, scopeId)) + } + return creds +} diff --git a/internal/credential/static/testing_test.go b/internal/credential/static/testing_test.go new file mode 100644 index 000000000..47e1f1ab5 --- /dev/null +++ b/internal/credential/static/testing_test.go @@ -0,0 +1,86 @@ +package static + +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/libs/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_TestCredentialStore(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 := TestCredentialStore(t, conn, wrapper, prj.GetPublicId(), WithName("my-name"), WithDescription("my-description")) + require.NotNil(cs) + assert.NotEmpty(cs.GetPublicId()) + assert.Equal(cs.Name, "my-name") + assert.Equal(cs.Description, "my-description") +} + +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) +} + +func Test_TestUsernamePasswordCredential(t *testing.T) { + t.Parallel() + assert, require := assert.New(t), require.New(t) + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + require.NotNil(prj) + assert.NotEmpty(prj.GetPublicId()) + + store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + cred := TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), WithName("my-name"), WithDescription("my-description")) + require.NotNil(cred) + assert.NotEmpty(cred.GetPublicId()) + assert.Equal(cred.Name, "my-name") + assert.Equal(cred.Description, "my-description") + assert.Equal(cred.Username, "user") + assert.Equal(cred.Password, []byte("pass")) + + // Validate hmac + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase) + hm, err := crypto.HmacSha256(context.Background(), cred.Password, databaseWrapper, []byte(cred.StoreId), nil, crypto.WithEd25519()) + require.NoError(err) + assert.Equal([]byte(hm), cred.PasswordHmac) +} + +func Test_TestUsernamePasswordCredentials(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()) + + store := TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + count := 4 + creds := TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId(), count) + assert.Len(creds, count) +} diff --git a/internal/credential/static/usernamepassword_credential.go b/internal/credential/static/usernamepassword_credential.go new file mode 100644 index 000000000..b5ad2fd73 --- /dev/null +++ b/internal/credential/static/usernamepassword_credential.go @@ -0,0 +1,121 @@ +package static + +import ( + "context" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static/store" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/libs/crypto" + "github.com/hashicorp/boundary/internal/oplog" + wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/go-kms-wrapping/v2/extras/structwrapping" + "google.golang.org/protobuf/proto" +) + +// A UsernamePasswordCredential contains the credential with a username and password. +// It is owned by a credential store. +type UsernamePasswordCredential struct { + *store.UsernamePasswordCredential + tableName string `gorm:"-"` +} + +// NewUsernamePasswordCredential creates a new in memory static Credential containing a +// username and password that is assigned to storeId. Name and description are the only +// valid options. All other options are ignored. +func NewUsernamePasswordCredential( + storeId string, + username string, + password credential.Password, + opt ...Option, +) (*UsernamePasswordCredential, error) { + opts := getOpts(opt...) + l := &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + StoreId: storeId, + Name: opts.withName, + Description: opts.withDescription, + Username: username, + Password: []byte(password), + }, + } + return l, nil +} + +func allocUsernamePasswordCredential() *UsernamePasswordCredential { + return &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{}, + } +} + +func (c *UsernamePasswordCredential) clone() *UsernamePasswordCredential { + cp := proto.Clone(c.UsernamePasswordCredential) + return &UsernamePasswordCredential{ + UsernamePasswordCredential: cp.(*store.UsernamePasswordCredential), + } +} + +// TableName returns the table name. +func (c *UsernamePasswordCredential) TableName() string { + if c.tableName != "" { + return c.tableName + } + return "credential_static_username_password_credential" +} + +// SetTableName sets the table name. +func (c *UsernamePasswordCredential) SetTableName(n string) { + c.tableName = n +} + +func (c *UsernamePasswordCredential) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{c.PublicId}, + "resource-type": []string{"credential-static-username-password"}, + "op-type": []string{op.String()}, + } + if c.StoreId != "" { + metadata["store-id"] = []string{c.StoreId} + } + return metadata +} + +func (c *UsernamePasswordCredential) encrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "static.(UsernamePasswordCredential).encrypt" + if len(c.Password) == 0 { + return errors.New(ctx, errors.InvalidParameter, op, "no password defined") + } + if err := structwrapping.WrapStruct(ctx, cipher, c.UsernamePasswordCredential, nil); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt)) + } + keyId, err := cipher.KeyId(ctx) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Encrypt), errors.WithMsg("error reading cipher key id")) + } + c.KeyId = keyId + if err := c.hmacPassword(ctx, cipher); err != nil { + return errors.Wrap(ctx, err, op) + } + return nil +} + +func (c *UsernamePasswordCredential) decrypt(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "static.(UsernamePasswordCredential).decrypt" + if err := structwrapping.UnwrapStruct(ctx, cipher, c.UsernamePasswordCredential, nil); err != nil { + return errors.Wrap(ctx, err, op, errors.WithCode(errors.Decrypt)) + } + return nil +} + +func (c *UsernamePasswordCredential) hmacPassword(ctx context.Context, cipher wrapping.Wrapper) error { + const op = "static.(UsernamePasswordCredential).hmacPassword" + if cipher == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing cipher") + } + hm, err := crypto.HmacSha256(ctx, c.Password, cipher, []byte(c.StoreId), nil, crypto.WithEd25519()) + if err != nil { + return errors.Wrap(ctx, err, op) + } + c.PasswordHmac = []byte(hm) + return nil +} diff --git a/internal/credential/static/usernamepassword_credential_test.go b/internal/credential/static/usernamepassword_credential_test.go new file mode 100644 index 000000000..c7596fde6 --- /dev/null +++ b/internal/credential/static/usernamepassword_credential_test.go @@ -0,0 +1,178 @@ +package static + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static/store" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/libs/crypto" + "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) + kkms := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + _, prj := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper)) + cs := TestCredentialStore(t, conn, wrapper, prj.PublicId) + + type args struct { + username string + password credential.Password + storeId string + options []Option + } + + tests := []struct { + name string + args args + want *UsernamePasswordCredential + wantCreateErr bool + wantEncryptErr bool + }{ + { + name: "missing-password", + args: args{ + username: "test-user", + storeId: cs.PublicId, + }, + want: allocUsernamePasswordCredential(), + wantEncryptErr: true, + }, + { + name: "missing-username", + args: args{ + password: "test-pass", + storeId: cs.PublicId, + }, + want: allocUsernamePasswordCredential(), + wantCreateErr: true, + }, + { + name: "missing-store-id", + args: args{ + username: "test-user", + password: "test-pass", + }, + want: allocUsernamePasswordCredential(), + wantCreateErr: true, + }, + { + name: "valid-no-options", + args: args{ + username: "test-user", + password: "test-pass", + storeId: cs.PublicId, + }, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-user", + Password: []byte("test-pass"), + StoreId: cs.PublicId, + }, + }, + }, + { + name: "valid-with-name", + args: args{ + username: "test-user", + password: "test-pass", + storeId: cs.PublicId, + options: []Option{WithName("my-credential")}, + }, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-user", + Password: []byte("test-pass"), + StoreId: cs.PublicId, + Name: "my-credential", + }, + }, + }, + { + name: "valid-with-description", + args: args{ + username: "test-user", + password: "test-pass", + storeId: cs.PublicId, + options: []Option{WithDescription("my-credential-description")}, + }, + want: &UsernamePasswordCredential{ + UsernamePasswordCredential: &store.UsernamePasswordCredential{ + Username: "test-user", + Password: []byte("test-pass"), + StoreId: cs.PublicId, + Description: "my-credential-description", + }, + }, + }, + } + + 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 := NewUsernamePasswordCredential(tt.args.storeId, tt.args.username, tt.args.password, tt.args.options...) + require.NoError(err) + require.NotNil(got) + assert.Emptyf(got.PublicId, "PublicId set") + + id, err := newCredentialId(ctx) + require.NoError(err) + + tt.want.PublicId = id + got.PublicId = id + + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase) + + err = got.encrypt(ctx, databaseWrapper) + if tt.wantEncryptErr { + require.Error(err) + return + } + assert.NoError(err) + + err = rw.Create(context.Background(), got) + if tt.wantCreateErr { + require.Error(err) + return + } + assert.NoError(err) + + got2 := allocUsernamePasswordCredential() + got2.PublicId = id + assert.Equal(id, got2.GetPublicId()) + require.NoError(rw.LookupById(ctx, got2)) + + err = got2.decrypt(ctx, databaseWrapper) + require.NoError(err) + + // Timestamps and version are automatically set + tt.want.CreateTime = got2.CreateTime + tt.want.UpdateTime = got2.UpdateTime + tt.want.Version = got2.Version + + // KeyId is allocated via kms no need to validate in this test + tt.want.KeyId = got2.KeyId + got2.CtPassword = nil + + // encrypt also calculates the hmac, validate it is correct + hm, err := crypto.HmacSha256(ctx, got.Password, databaseWrapper, []byte(got.StoreId), nil, crypto.WithEd25519()) + require.NoError(err) + tt.want.PasswordHmac = []byte(hm) + + assert.Empty(cmp.Diff(tt.want, got2.clone(), protocmp.Transform())) + }) + } +} diff --git a/internal/credential/vault/private_library.go b/internal/credential/vault/private_library.go index 76f750517..ba05a45c3 100644 --- a/internal/credential/vault/private_library.go +++ b/internal/credential/vault/private_library.go @@ -18,7 +18,7 @@ import ( "google.golang.org/protobuf/proto" ) -var _ credential.UserPassword = (*usrPassCred)(nil) +var _ credential.UsernamePassword = (*usrPassCred)(nil) type usrPassCred struct { *baseCred diff --git a/internal/credential/vault/repository_credentials_test.go b/internal/credential/vault/repository_credentials_test.go index 69cc145de..2fa094eb6 100644 --- a/internal/credential/vault/repository_credentials_test.go +++ b/internal/credential/vault/repository_credentials_test.go @@ -327,14 +327,14 @@ func TestRepository_IssueCredentials(t *testing.T) { for _, dc := range got { switch dc.Library().CredentialType() { case credential.UsernamePasswordType: - if upc, ok := dc.(credential.UserPassword); ok { + if upc, ok := dc.(credential.UsernamePassword); ok { assert.NotEmpty(upc.Username()) assert.NotEmpty(upc.Password()) break } - assert.Fail("want UserPassword credential from library with credential type UserPassword") + assert.Fail("want UserPassword credential from library with credential type UsernamePassword") case credential.UnspecifiedType: - if _, ok := dc.(credential.UserPassword); ok { + if _, ok := dc.(credential.UsernamePassword); ok { assert.Fail("do not want UserPassword credential from library with credential type Unspecified") } } diff --git a/internal/daemon/controller/common/common.go b/internal/daemon/controller/common/common.go index 4945b44d9..4499ec908 100644 --- a/internal/daemon/controller/common/common.go +++ b/internal/daemon/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" + credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" pluginhost "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" @@ -14,16 +15,17 @@ import ( ) type ( - 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) - PluginHostRepoFactory func() (*pluginhost.Repository, error) - HostPluginRepoFactory func() (*hostplugin.Repository, error) - SessionRepoFactory func() (*session.Repository, error) - ConnectionRepoFactory func() (*session.ConnectionRepository, error) - TargetRepoFactory func() (*target.Repository, error) + AuthTokenRepoFactory = oidc.AuthTokenRepoFactory + VaultCredentialRepoFactory = func() (*vault.Repository, error) + StaticCredentialRepoFactory = func() (*credstatic.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) + PluginHostRepoFactory func() (*pluginhost.Repository, error) + HostPluginRepoFactory func() (*hostplugin.Repository, error) + SessionRepoFactory func() (*session.Repository, error) + ConnectionRepoFactory func() (*session.ConnectionRepository, error) + TargetRepoFactory func() (*target.Repository, error) ) diff --git a/internal/daemon/controller/controller.go b/internal/daemon/controller/controller.go index 9fa9162dc..1284c11f1 100644 --- a/internal/daemon/controller/controller.go +++ b/internal/daemon/controller/controller.go @@ -12,6 +12,7 @@ import ( "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/cmd/base" "github.com/hashicorp/boundary/internal/cmd/config" + credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/common" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/health" @@ -64,18 +65,19 @@ type Controller struct { apiGrpcGatewayTicket string // Repo factory methods - AuthTokenRepoFn common.AuthTokenRepoFactory - VaultCredentialRepoFn common.VaultCredentialRepoFactory - IamRepoFn common.IamRepoFactory - OidcRepoFn common.OidcAuthRepoFactory - PasswordAuthRepoFn common.PasswordAuthRepoFactory - ServersRepoFn common.ServersRepoFactory - SessionRepoFn common.SessionRepoFactory - ConnectionRepoFn common.ConnectionRepoFactory - StaticHostRepoFn common.StaticRepoFactory - PluginHostRepoFn common.PluginHostRepoFactory - HostPluginRepoFn common.HostPluginRepoFactory - TargetRepoFn common.TargetRepoFactory + AuthTokenRepoFn common.AuthTokenRepoFactory + VaultCredentialRepoFn common.VaultCredentialRepoFactory + StaticCredentialRepoFn common.StaticCredentialRepoFactory + IamRepoFn common.IamRepoFactory + OidcRepoFn common.OidcAuthRepoFactory + PasswordAuthRepoFn common.PasswordAuthRepoFactory + ServersRepoFn common.ServersRepoFactory + SessionRepoFn common.SessionRepoFactory + ConnectionRepoFn common.ConnectionRepoFactory + StaticHostRepoFn common.StaticRepoFactory + PluginHostRepoFn common.PluginHostRepoFactory + HostPluginRepoFn common.HostPluginRepoFactory + TargetRepoFn common.TargetRepoFactory scheduler *scheduler.Scheduler @@ -276,6 +278,9 @@ func New(ctx context.Context, conf *Config) (*Controller, error) { c.VaultCredentialRepoFn = func() (*vault.Repository, error) { return vault.NewRepository(dbase, dbase, c.kms, c.scheduler) } + c.StaticCredentialRepoFn = func() (*credstatic.Repository, error) { + return credstatic.NewRepository(ctx, dbase, dbase, c.kms) + } c.ServersRepoFn = func() (*servers.Repository, error) { return servers.NewRepository(dbase, dbase, c.kms) } diff --git a/internal/daemon/controller/handler.go b/internal/daemon/controller/handler.go index e5218278c..0c9c0f973 100644 --- a/internal/daemon/controller/handler.go +++ b/internal/daemon/controller/handler.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentialstores" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/groups" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/health" @@ -209,7 +210,7 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { services.RegisterManagedGroupServiceServer(s, mgs) } if _, ok := currentServices[services.CredentialStoreService_ServiceDesc.ServiceName]; !ok { - cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.IamRepoFn) + cs, err := credentialstores.NewService(c.VaultCredentialRepoFn, c.StaticCredentialRepoFn, c.IamRepoFn) if err != nil { return fmt.Errorf("failed to create credential store handler service: %w", err) } @@ -222,6 +223,13 @@ func (c *Controller) registerGrpcServices(s *grpc.Server) error { } services.RegisterCredentialLibraryServiceServer(s, cl) } + if _, ok := currentServices[services.CredentialService_ServiceDesc.ServiceName]; !ok { + c, err := credentials.NewService(c.StaticCredentialRepoFn, c.IamRepoFn) + if err != nil { + return fmt.Errorf("failed to create credential handler service: %w", err) + } + services.RegisterCredentialServiceServer(s, c) + } if _, ok := s.GetServiceInfo()[opsservices.HealthService_ServiceDesc.ServiceName]; !ok { hs := health.NewService() opsservices.RegisterHealthServiceServer(s, hs) @@ -279,6 +287,9 @@ func registerGrpcGatewayEndpoints(ctx context.Context, gwMux *runtime.ServeMux, if err := services.RegisterCredentialLibraryServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil { return fmt.Errorf("failed to register credential library service handler: %w", err) } + if err := services.RegisterCredentialServiceHandlerFromEndpoint(ctx, gwMux, gatewayTarget, dialOptions); err != nil { + return fmt.Errorf("failed to register credential service handler: %w", err) + } return nil } diff --git a/internal/daemon/controller/handlers/credentials/credential_service.go b/internal/daemon/controller/handlers/credentials/credential_service.go new file mode 100644 index 000000000..a45f9cd7d --- /dev/null +++ b/internal/daemon/controller/handlers/credentials/credential_service.go @@ -0,0 +1,592 @@ +package credentials + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/credential/static/store" + "github.com/hashicorp/boundary/internal/daemon/controller/auth" + "github.com/hashicorp/boundary/internal/daemon/controller/common" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers" + "github.com/hashicorp/boundary/internal/errors" + 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/types/action" + "github.com/hashicorp/boundary/internal/types/resource" + "github.com/hashicorp/boundary/internal/types/subtypes" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" + "google.golang.org/grpc/codes" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +const ( + usernameField = "attributes.username" + passwordField = "attributes.password" + domain = "credential" +) + +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.UsernamePasswordCredential{}}, + handlers.MaskSource{&pb.Credential{}, &pb.UsernamePasswordAttributes{}}); err != nil { + panic(err) + } +} + +// Service handles request as described by the pbs.CredentialServiceServer interface. +type Service struct { + pbs.UnimplementedCredentialServiceServer + + iamRepoFn common.IamRepoFactory + repoFn common.StaticCredentialRepoFactory +} + +// NewService returns a credential service which handles credential related requests to boundary. +func NewService(repo common.StaticCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) { + const op = "credentials.NewService" + if iamRepo == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository") + } + if repo == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static credential repository") + } + return Service{iamRepoFn: iamRepo, repoFn: repo}, nil +} + +var _ pbs.CredentialServiceServer = Service{} + +// ListCredentials implements the interface pbs.CredentialServiceServer +func (s Service) ListCredentials(ctx context.Context, req *pbs.ListCredentialsRequest) (*pbs.ListCredentialsResponse, 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 + } + + creds, err := s.listFromRepo(ctx, req.GetCredentialStoreId()) + if err != nil { + return nil, err + } + if len(creds) == 0 { + return &pbs.ListCredentialsResponse{}, nil + } + + filter, err := handlers.NewFilter(req.GetFilter()) + if err != nil { + return nil, err + } + finalItems := make([]*pb.Credential, 0, len(creds)) + res := perms.Resource{ + ScopeId: authResults.Scope.Id, + Type: resource.Credential, + Pin: req.GetCredentialStoreId(), + } + for _, item := range creds { + 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 + } + + filterable, err := subtypes.Filterable(item) + if err != nil { + return nil, err + } + if filter.Match(filterable) { + finalItems = append(finalItems, item) + } + } + return &pbs.ListCredentialsResponse{Items: finalItems}, nil +} + +// GetCredential implements the interface pbs.CredentialServiceServer. +func (s Service) GetCredential(ctx context.Context, req *pbs.GetCredentialRequest) (*pbs.GetCredentialResponse, error) { + const op = "credentials.(Service).GetCredential" + + 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 + } + c, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(ctx, 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, c.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(c, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.GetCredentialResponse{Item: item}, nil +} + +// CreateCredential implements the interface pbs.CredentialServiceServer. +func (s Service) CreateCredential(ctx context.Context, req *pbs.CreateCredentialRequest) (*pbs.CreateCredentialResponse, error) { + const op = "credentials.(Service).CreateCredential" + + 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(ctx, 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.CreateCredentialResponse{ + Item: item, + Uri: fmt.Sprintf("credentials/%s", item.GetId()), + }, nil +} + +// UpdateCredential implements the interface pbs.CredentialServiceServer. +func (s Service) UpdateCredential(ctx context.Context, req *pbs.UpdateCredentialRequest) (*pbs.UpdateCredentialResponse, error) { + const op = "credentials.(Service).UpdateCredential" + + cur, err := s.getFromRepo(ctx, req.GetId()) + if err != nil { + return nil, err + } + storeId := cur.GetStoreId() + + 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 + } + c, err := s.updateInRepo(ctx, authResults.Scope.GetId(), storeId, req.GetId(), req.GetUpdateMask().GetPaths(), req.GetItem()) + if err != nil { + return nil, err + } + + outputFields, ok := requests.OutputFields(ctx) + if !ok { + return nil, errors.New(ctx, 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, c.GetPublicId(), IdActions).Strings())) + } + + item, err := toProto(c, outputOpts...) + if err != nil { + return nil, err + } + + return &pbs.UpdateCredentialResponse{Item: item}, nil +} + +// DeleteCredential implements the interface pbs.CredentialServiceServer. +func (s Service) DeleteCredential(ctx context.Context, req *pbs.DeleteCredentialRequest) (*pbs.DeleteCredentialResponse, 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) ([]credential.Static, error) { + const op = "credentials.(Service).listFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + up, err := repo.ListCredentials(ctx, storeId) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + creds := make([]credential.Static, 0, len(up)) + for _, s := range up { + creds = append(creds, s) + } + + return creds, nil +} + +func (s Service) getFromRepo(ctx context.Context, id string) (credential.Static, error) { + const op = "credentials.(Service).getFromRepo" + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cred, err := repo.LookupCredential(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return nil, errors.Wrap(ctx, err, op) + } + if cred == nil { + return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential %q not found", id)) + } + return cred, err +} + +func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Credential) (credential.Static, error) { + const op = "credentials.(Service).createInRepo" + switch item.GetType() { + case static.UsernamePasswordSubtype.String(): + cred, err := toUsernamePasswordStorageCredential(item.GetCredentialStoreId(), item) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, err := repo.CreateUsernamePasswordCredential(ctx, scopeId, cred) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential")) + } + if out == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.") + } + return out, nil + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType())) + } +} + +func (s Service) updateInRepo( + ctx context.Context, + scopeId, storeId, id string, + masks []string, + in *pb.Credential, +) (credential.Static, error) { + const op = "credentials.(Service).updateInRepo" + + var dbMasks []string + item := proto.Clone(in).(*pb.Credential) + + dbMasks = append(dbMasks, maskManager.Translate(masks)...) + if len(dbMasks) == 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."}) + } + + switch subtypes.SubtypeFromId(domain, id) { + case static.UsernamePasswordSubtype: + cred, err := toUsernamePasswordStorageCredential(storeId, in) + cred.PublicId = id + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, rowsUpdated, err := repo.UpdateUsernamePasswordCredential(ctx, scopeId, cred, item.GetVersion(), dbMasks) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential")) + } + if rowsUpdated == 0 { + return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id) + } + return out, nil + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, fmt.Sprintf("Unsupported credential type %q", item.GetType())) + + } +} + +func (s Service) deleteFromRepo(ctx context.Context, scopeId, id string) (bool, error) { + const op = "credentials.(Service).deleteFromRepo" + repo, err := s.repoFn() + if err != nil { + return false, err + } + + rows, err := repo.DeleteCredential(ctx, scopeId, id) + if err != nil { + if errors.IsNotFoundError(err) { + return false, nil + } + return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential")) + } + return rows > 0, nil +} + +func (s Service) authResult(ctx context.Context, id string, a action.Type) auth.VerifyResults { + const op = "credentials.(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.Credential), auth.WithAction(a)} + switch a { + case action.List, action.Create: + parentId = id + default: + opts = append(opts, auth.WithId(id)) + cred, err := repo.LookupCredential(ctx, id) + if err != nil { + res.Error = err + return res + } + if cred == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cred.GetStoreId() + + } + + if parentId == "" { + res.Error = errors.New(ctx, errors.RecordNotFound, op, "unable to find credential store for provided credential") + return res + } + opts = append(opts, auth.WithPin(parentId)) + + 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())) + + return auth.Verify(ctx, opts...) +} + +func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, error) { + opts := handlers.GetOpts(opt...) + if opts.WithOutputFields == nil { + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "output fields not found when building credential proto") + } + outputFields := *opts.WithOutputFields + + out := pb.Credential{} + if outputFields.Has(globals.IdField) { + out.Id = in.GetPublicId() + } + if outputFields.Has(globals.CredentialStoreIdField) { + out.CredentialStoreId = in.GetStoreId() + } + if outputFields.Has(globals.TypeField) { + switch in.(type) { + case *static.UsernamePasswordCredential: + out.Type = static.UsernamePasswordSubtype.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 + } + + switch cred := in.(type) { + case *static.UsernamePasswordCredential: + if outputFields.Has(globals.AttributesField) { + out.Attrs = &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String(cred.GetUsername()), + PasswordHmac: base64.RawURLEncoding.EncodeToString(cred.GetPasswordHmac()), + }, + } + } + } + return &out, nil +} + +func toUsernamePasswordStorageCredential(storeId string, in *pb.Credential) (out *static.UsernamePasswordCredential, err error) { + const op = "credentials.toStorageCredential" + var opts []static.Option + if in.GetName() != nil { + opts = append(opts, static.WithName(in.GetName().GetValue())) + } + if in.GetDescription() != nil { + opts = append(opts, static.WithDescription(in.GetDescription().GetValue())) + } + + attrs := in.GetUsernamePasswordAttributes() + cs, err := static.NewUsernamePasswordCredential( + storeId, + attrs.GetUsername().GetValue(), + credential.Password(attrs.GetPassword().GetValue()), + opts...) + if err != nil { + return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential")) + } + + 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.GetCredentialRequest) error { + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, static.CredentialPrefix) +} + +func validateCreateRequest(req *pbs.CreateCredentialRequest) error { + return handlers.ValidateCreateRequest(req.GetItem(), func() map[string]string { + badFields := map[string]string{} + if req.Item.GetType() != static.UsernamePasswordSubtype.String() { + badFields[globals.TypeField] = fmt.Sprintf("Unsupported credential type %q", req.Item.GetType()) + } + if req.Item.GetCredentialStoreId() == "" { + badFields[globals.CredentialStoreIdField] = "This field must be a valid credential store id." + } + + if req.Item.GetUsernamePasswordAttributes().GetUsername().GetValue() == "" { + badFields[usernameField] = "Field required for creating a username-password credential." + } + if req.Item.GetUsernamePasswordAttributes().GetPassword().GetValue() == "" { + badFields[passwordField] = "Field required for creating a username-password credential." + } + + return badFields + }) +} + +func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error { + return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string { + badFields := map[string]string{} + if req.GetItem().GetType() != "" && req.GetItem().GetType() != static.UsernamePasswordSubtype.String() { + badFields[globals.TypeField] = "Cannot modify resource type." + } + + attrs := req.GetItem().GetUsernamePasswordAttributes() + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" { + badFields[usernameField] = "This is a required field and cannot be set to empty." + } + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" { + badFields[passwordField] = "This is a required field and cannot be set to empty." + } + + return badFields + }, static.CredentialPrefix) +} + +func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error { + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, static.CredentialPrefix) +} + +func validateListRequest(req *pbs.ListCredentialsRequest) error { + badFields := map[string]string{} + if !handlers.ValidId(handlers.Id(req.GetCredentialStoreId()), static.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/daemon/controller/handlers/credentials/credential_service_test.go b/internal/daemon/controller/handlers/credentials/credential_service_test.go new file mode 100644 index 000000000..96cc51cc0 --- /dev/null +++ b/internal/daemon/controller/handlers/credentials/credential_service_test.go @@ -0,0 +1,742 @@ +package credentials + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/credential/vault" + "github.com/hashicorp/boundary/internal/daemon/controller/auth" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers" + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/errors" + pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" + "github.com/hashicorp/boundary/internal/iam" + "github.com/hashicorp/boundary/internal/kms" + "github.com/hashicorp/boundary/internal/libs/crypto" + "github.com/hashicorp/boundary/internal/scheduler" + "github.com/hashicorp/boundary/internal/types/scope" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" + scopepb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + "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/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) + kkms := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(context.Background(), rw, rw, kkms) + } + + _, prj := iam.TestScopes(t, iamRepo) + store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + storeNoCreds := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + var wantCreds []*pb.Credential + for i := 0; i < 10; i++ { + user := fmt.Sprintf("user-%d", i) + pass := fmt.Sprintf("pass-%d", i) + c := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId()) + + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase) + require.NoError(t, err) + hm, err := crypto.HmacSha256(context.Background(), []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + + wantCreds = append(wantCreds, &pb.Credential{ + Id: c.GetPublicId(), + CredentialStoreId: store.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: c.GetCreateTime().GetTimestamp(), + UpdatedTime: c.GetUpdateTime().GetTimestamp(), + Version: c.GetVersion(), + Type: static.UsernamePasswordSubtype.String(), + AuthorizedActions: testAuthorizedActions, + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String(c.GetUsername()), + PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)), + }, + }, + }) + } + + cases := []struct { + name string + req *pbs.ListCredentialsRequest + res *pbs.ListCredentialsResponse + anonRes *pbs.ListCredentialsResponse + err error + }{ + { + name: "List many credentials", + req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId()}, + res: &pbs.ListCredentialsResponse{Items: wantCreds}, + anonRes: &pbs.ListCredentialsResponse{Items: wantCreds}, + }, + { + name: "List no credentials", + req: &pbs.ListCredentialsRequest{CredentialStoreId: storeNoCreds.GetPublicId()}, + res: &pbs.ListCredentialsResponse{}, + anonRes: &pbs.ListCredentialsResponse{}, + }, + { + name: "Filter to one credential", + req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantCreds[1].GetId())}, + res: &pbs.ListCredentialsResponse{Items: wantCreds[1:2]}, + anonRes: &pbs.ListCredentialsResponse{Items: wantCreds[1:2]}, + }, + { + name: "Filter on Attribute", + req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[2].GetUsernamePasswordAttributes().GetUsername().Value)}, + res: &pbs.ListCredentialsResponse{Items: wantCreds[2:3]}, + anonRes: &pbs.ListCredentialsResponse{}, // anonymous user does not have access to attributes + }, + { + name: "Filter to no credential", + req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, + res: &pbs.ListCredentialsResponse{}, + anonRes: &pbs.ListCredentialsResponse{}, + }, + { + name: "Filter Bad Format", + req: &pbs.ListCredentialsRequest{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(staticRepoFn, iamRepoFn) + require.NoError(t, err, "Couldn't create new host set service.") + + // Test non-anonymous listing + got, gErr := s.ListCredentials(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), 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(), protocmp.SortRepeated(func(x, y *pb.Credential) bool { + return x.Id < y.Id + }))) + + // Test anonymous listing + got, gErr = s.ListCredentials(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId(), auth.WithUserId(auth.AnonymousUserId)), tc.req) + require.NoError(t, gErr) + assert.Len(t, got.Items, len(tc.anonRes.Items)) + for _, item := range got.GetItems() { + require.Nil(t, item.CreatedTime) + require.Nil(t, item.UpdatedTime) + require.Zero(t, item.Version) + } + }) + } +} + +func TestGet(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(context.Background(), rw, rw, kkms) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId()) + s, err := NewService(staticRepoFn, iamRepoFn) + require.NoError(t, err) + + // Calculate hmac + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase) + require.NoError(t, err) + hm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + + cases := []struct { + name string + id string + res *pbs.GetCredentialResponse + err error + }{ + { + name: "success", + id: cred.GetPublicId(), + res: &pbs.GetCredentialResponse{ + Item: &pb.Credential{ + Id: cred.GetPublicId(), + CredentialStoreId: cred.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: store.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: static.UsernamePasswordSubtype.String(), + AuthorizedActions: testAuthorizedActions, + CreatedTime: cred.CreateTime.GetTimestamp(), + UpdatedTime: cred.UpdateTime.GetTimestamp(), + Version: 1, + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("user"), + PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hm)), + }, + }, + }, + }, + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", static.CredentialPrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.CredentialStorePrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &pbs.GetCredentialRequest{Id: tc.id} + // Test non-anonymous get + got, gErr := s.GetCredential(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.GetCredential(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) + err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) + require.NoError(t, err) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(context.Background(), rw, rw, kms) + } + + _, prj := iam.TestScopes(t, iamRepo) + + store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + cred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId()) + s, err := NewService(staticRepoFn, iamRepoFn) + require.NoError(t, err) + + cases := []struct { + name string + id string + err error + res *pbs.DeleteCredentialResponse + }{ + { + name: "success", + id: cred.GetPublicId(), + }, + { + name: "not found error", + id: fmt.Sprintf("%s_1234567890", static.CredentialPrefix), + err: handlers.NotFoundError(), + }, + { + name: "bad prefix", + id: fmt.Sprintf("%s_1234567890", static.CredentialStorePrefix), + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, gErr := s.DeleteCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.DeleteCredentialRequest{Id: tc.id}) + assert.EqualValuesf(t, tc.res, got, "DeleteCredentialStore got response %q, wanted %q", got, tc.res) + if tc.err != nil { + require.Error(t, gErr) + assert.True(t, errors.Is(gErr, tc.err)) + return + } + require.NoError(t, gErr) + g, err := s.GetCredential(auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()), &pbs.GetCredentialRequest{Id: tc.id}) + assert.Nil(t, g) + assert.True(t, errors.Is(err, handlers.NotFoundError())) + }) + } +} + +func TestCreate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + repoFn := func() (*static.Repository, error) { + return static.NewRepository(context.Background(), rw, rw, kkms) + } + + _, prj := iam.TestScopes(t, iamRepo) + store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + cases := []struct { + name string + req *pbs.CreateCredentialRequest + res *pbs.CreateCredentialResponse + idPrefix string + err error + wantErr bool + }{ + { + name: "Can't specify Id", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Id: static.CredentialPrefix + "_notallowed", + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + Password: wrapperspb.String("password"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Created Time", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + CreatedTime: timestamppb.Now(), + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Can't specify Updated Time", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + UpdatedTime: timestamppb.Now(), + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + Password: wrapperspb.String("password"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must provide type", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + Password: wrapperspb.String("password"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must provide username", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Password: wrapperspb.String("password"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must provide password]", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "valid", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Type: static.UsernamePasswordSubtype.String(), + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("username"), + Password: wrapperspb.String("password"), + }, + }, + }}, + idPrefix: static.CredentialPrefix + "_", + res: &pbs.CreateCredentialResponse{ + Uri: fmt.Sprintf("credentials/%s_", static.CredentialPrefix), + Item: &pb.Credential{ + 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: static.UsernamePasswordSubtype.String(), + 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.CreateCredential(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), "CreateCredential(...) 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(store.CreateTime.AsTime()), "New credential should have been created after default credential store. Was created %v, which is after %v", gotCreateTime, store.CreateTime.AsTime()) + assert.True(gotUpdateTime.AsTime().After(store.CreateTime.AsTime()), "New credential should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, store.CreateTime.AsTime()) + + // Calculate hmac + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.PublicId, kms.KeyPurposeDatabase) + password := tc.req.GetItem().GetUsernamePasswordAttributes().GetPassword().GetValue() + hm, err := crypto.HmacSha256(context.Background(), []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(err) + + // Validate attributes equal + assert.Equal(tc.req.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue(), + got.GetItem().GetUsernamePasswordAttributes().GetUsername().GetValue()) + assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac()) + assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword()) + + // Clear attributes for compare below + got.Item.Attrs = nil + + // 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)), "CreateCredential(%q) got response %q, wanted %q", tc.req, got, tc.res) + }) + } +} + +func TestUpdate(t *testing.T) { + conn, _ := db.TestSetup(t, "postgres") + wrapper := db.TestWrapper(t) + kkms := kms.TestKms(t, conn, wrapper) + rw := db.New(conn) + + iamRepo := iam.TestRepo(t, conn, wrapper) + iamRepoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + staticRepoFn := func() (*static.Repository, error) { + return static.NewRepository(context.Background(), rw, rw, kkms) + } + + _, prj := iam.TestScopes(t, iamRepo) + ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) + + s, err := NewService(staticRepoFn, iamRepoFn) + require.NoError(t, err) + + fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { + return &fieldmaskpb.FieldMask{Paths: paths} + } + + store := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + freshCred := func(user, pass string) (*static.UsernamePasswordCredential, func()) { + t.Helper() + cred := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId()) + clean := func() { + _, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()}) + require.NoError(t, err) + } + return cred, clean + } + + databaseWrapper, err := kkms.GetWrapper(context.Background(), prj.GetPublicId(), kms.KeyPurposeDatabase) + require.NoError(t, err) + + successCases := []struct { + name string + req *pbs.UpdateCredentialRequest + res func(cred *pb.Credential) *pb.Credential + }{ + { + name: "name", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("name"), + Item: &pb.Credential{ + Name: wrapperspb.String("new-name"), + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + out.Name = wrapperspb.String("new-name") + return out + }, + }, + { + name: "description", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("description"), + Item: &pb.Credential{ + Description: wrapperspb.String("new-description"), + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + out.Description = wrapperspb.String("new-description") + return out + }, + }, + { + name: "name-and-description", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("name", "description"), + Item: &pb.Credential{ + Name: wrapperspb.String("new-name"), + Description: wrapperspb.String("new-description"), + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + out.Name = wrapperspb.String("new-name") + out.Description = wrapperspb.String("new-description") + return out + }, + }, + { + name: "update-username", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("attributes.username"), + Item: &pb.Credential{ + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("new-user-name"), + }, + }, + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + out.GetUsernamePasswordAttributes().Username = wrapperspb.String("new-user-name") + return out + }, + }, + { + name: "update-password", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("attributes.password"), + Item: &pb.Credential{ + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Password: wrapperspb.String("new-password"), + }, + }, + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + hm, err := crypto.HmacSha256(context.Background(), []byte("new-password"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + out.GetUsernamePasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm)) + return out + }, + }, + { + name: "update-username-and-password", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("attributes.username", "attributes.password"), + Item: &pb.Credential{ + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: wrapperspb.String("new-username"), + Password: wrapperspb.String("new-password"), + }, + }, + }, + }, + res: func(in *pb.Credential) *pb.Credential { + out := proto.Clone(in).(*pb.Credential) + + out.GetUsernamePasswordAttributes().Username = wrapperspb.String("new-username") + + hm, err := crypto.HmacSha256(context.Background(), []byte("new-password"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + out.GetUsernamePasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm)) + return out + }, + }, + } + + for _, tc := range successCases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + cred, cleanup := freshCred("user", "pass") + defer cleanup() + + if tc.req.Item.GetVersion() == 0 { + tc.req.Item.Version = 1 + } + if tc.req.GetId() == "" { + tc.req.Id = cred.GetPublicId() + } + resToChange, err := s.GetCredential(ctx, &pbs.GetCredentialRequest{Id: cred.GetPublicId()}) + require.NoError(err) + want := &pbs.UpdateCredentialResponse{Item: tc.res(resToChange.GetItem())} + + got, gErr := s.UpdateCredential(ctx, tc.req) + require.NoError(gErr) + require.NotNil(got) + + gotUpdateTime := got.GetItem().GetUpdatedTime() + created := cred.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())) + }) + } + + // cant update read only fields + cred, cleanup := freshCred("user", "pass") + defer cleanup() + + newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + roCases := []struct { + path string + item *pb.Credential + matcher func(t *testing.T, e error) // When not set defaults to checking against InvalidArgument Error + }{ + { + path: "type", + item: &pb.Credential{Type: "something"}, + }, + { + path: "store_id", + item: &pb.Credential{CredentialStoreId: newStore.GetPublicId()}, + }, + { + path: "updated_time", + item: &pb.Credential{UpdatedTime: timestamppb.Now()}, + }, + { + path: "created_time", + item: &pb.Credential{UpdatedTime: timestamppb.Now()}, + }, + { + path: "authorized_actions", + item: &pb.Credential{AuthorizedActions: append(testAuthorizedActions, "another")}, + }, + } + for _, tc := range roCases { + t.Run(fmt.Sprintf("ReadOnlyField/%s", tc.path), func(t *testing.T) { + req := &pbs.UpdateCredentialRequest{ + Id: cred.GetPublicId(), + Item: tc.item, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}}, + } + req.Item.Version = cred.Version + + got, gErr := s.UpdateCredential(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/daemon/controller/handlers/credentialstores/credentialstore_service.go b/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go index 54ff1419f..c5769a004 100644 --- a/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go +++ b/internal/daemon/controller/handlers/credentialstores/credentialstore_service.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/credential/vault/store" "github.com/hashicorp/boundary/internal/daemon/controller/auth" @@ -15,6 +16,7 @@ import ( "github.com/hashicorp/boundary/internal/daemon/controller/common/scopeids" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials" "github.com/hashicorp/boundary/internal/errors" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/perms" @@ -25,6 +27,7 @@ import ( "github.com/hashicorp/boundary/internal/types/subtypes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentialstores" "google.golang.org/grpc/codes" + "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -57,9 +60,12 @@ var ( action.List, } - collectionTypeMap = map[resource.Type]action.ActionSet{ + vaultCollectionTypeMap = map[resource.Type]action.ActionSet{ resource.CredentialLibrary: credentiallibraries.CollectionActions, } + staticCollectionTypeMap = map[resource.Type]action.ActionSet{ + resource.Credential: credentials.CollectionActions, + } ) func init() { @@ -74,20 +80,28 @@ func init() { type Service struct { pbs.UnimplementedCredentialStoreServiceServer - iamRepoFn common.IamRepoFactory - repoFn common.VaultCredentialRepoFactory + iamRepoFn common.IamRepoFactory + vaultRepoFn common.VaultCredentialRepoFactory + staticRepoFn common.StaticCredentialRepoFactory } // NewService returns a credential store service which handles credential store related requests to boundary. -func NewService(repo common.VaultCredentialRepoFactory, iamRepo common.IamRepoFactory) (Service, error) { +func NewService( + vaultRepo common.VaultCredentialRepoFactory, + staticRepo common.StaticCredentialRepoFactory, + iamRepo common.IamRepoFactory, +) (Service, error) { const op = "credentialstores.NewService" if iamRepo == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing iam repository") } - if repo == nil { + if vaultRepo == nil { return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing vault credential repository") } - return Service{iamRepoFn: iamRepo, repoFn: repo}, nil + if staticRepo == nil { + return Service{}, errors.NewDeprecated(errors.InvalidParameter, op, "missing static credential repository") + } + return Service{iamRepoFn: iamRepo, vaultRepoFn: vaultRepo, staticRepoFn: staticRepo}, nil } var _ pbs.CredentialStoreServiceServer = Service{} @@ -155,7 +169,7 @@ func (s Service) ListCredentialStores(ctx context.Context, req *pbs.ListCredenti outputOpts = append(outputOpts, handlers.WithAuthorizedActions(authorizedActions)) } if outputFields.Has(globals.AuthorizedCollectionActionsField) { - collectionActions, err := auth.CalculateAuthorizedCollectionActions(ctx, authResults, collectionTypeMap, authResults.Scope.Id, item.GetPublicId()) + collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, item.GetPublicId()) if err != nil { return nil, err } @@ -208,7 +222,7 @@ func (s Service) GetCredentialStore(ctx context.Context, req *pbs.GetCredentialS 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()) + collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId()) if err != nil { return nil, err } @@ -253,7 +267,7 @@ func (s Service) CreateCredentialStore(ctx context.Context, req *pbs.CreateCrede 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()) + collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId()) if err != nil { return nil, err } @@ -301,7 +315,7 @@ func (s Service) UpdateCredentialStore(ctx context.Context, req *pbs.UpdateCrede 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()) + collectionActions, err := calculateAuthorizedCollectionActions(ctx, authResults, cs.GetPublicId()) if err != nil { return nil, err } @@ -332,74 +346,154 @@ func (s Service) DeleteCredentialStore(ctx context.Context, req *pbs.DeleteCrede return nil, nil } -func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]*vault.CredentialStore, error) { +func (s Service) listFromRepo(ctx context.Context, scopeIds []string) ([]credential.Store, error) { const op = "credentialstores.(Service).listFromRepo" - repo, err := s.repoFn() + + vaultRepo, err := s.vaultRepoFn() if err != nil { return nil, errors.Wrap(ctx, err, op) } - csl, err := repo.ListCredentialStores(ctx, scopeIds) + vaultCsl, err := vaultRepo.ListCredentialStores(ctx, scopeIds) if err != nil { return nil, errors.Wrap(ctx, err, op) } + + staticRepo, err := s.staticRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + staticCsl, err := staticRepo.ListCredentialStores(ctx, scopeIds) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + + csl := make([]credential.Store, 0, len(staticCsl)+len(vaultCsl)) + for _, s := range vaultCsl { + csl = append(csl, s) + } + for _, s := range staticCsl { + csl = append(csl, s) + } + 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(ctx, err, op) + + switch subtypes.SubtypeFromId(domain, id) { + case vault.Subtype: + repo, err := s.vaultRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cs, err := repo.LookupCredentialStore(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return nil, errors.Wrap(ctx, err, op) + } + if cs != nil { + return cs, nil + } + + case static.Subtype: + repo, err := s.staticRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cs, err := repo.LookupCredentialStore(ctx, id) + if err != nil && !errors.IsNotFoundError(err) { + return nil, errors.Wrap(ctx, err, op) + } + if cs != nil { + return cs, nil + } } - cs, err := repo.LookupCredentialStore(ctx, id) - if err != nil && !errors.IsNotFoundError(err) { - return nil, errors.Wrap(ctx, err, op) - } - if cs == nil { - return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("credential store %q not found", id)) - } - return cs, err + + return nil, handlers.NotFoundErrorf("credential store %q not found", id) } 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(ctx, err, op) + + switch item.Type { + case vault.Subtype.String(): + cs, err := toStorageVaultStore(projId, item) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + repo, err := s.vaultRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, err := repo.CreateCredentialStore(ctx, cs) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential store")) + } + return out, nil + + case static.Subtype.String(): + cs, err := toStorageStaticStore(projId, item) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + repo, err := s.staticRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, err := repo.CreateCredentialStore(ctx, cs) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create credential store")) + } + return out, nil + + default: + return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential store, unknown type.") } - repo, err := s.repoFn() - if err != nil { - return nil, errors.Wrap(ctx, err, op) - } - out, err := repo.CreateCredentialStore(ctx, cs) - if err != nil { - return nil, errors.Wrap(ctx, 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(ctx, err, op) - } - cs.PublicId = id + + var out credential.Store + var rowsUpdated int 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(ctx, err, op) - } - out, rowsUpdated, err := repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask) - if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential store")) + + switch subtypes.SubtypeFromId(domain, id) { + case vault.Subtype: + cs, err := toStorageVaultStore(projId, item) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cs.PublicId = id + + repo, err := s.vaultRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, rowsUpdated, err = repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to update credential store")) + } + + case static.Subtype: + cs, err := toStorageStaticStore(projId, item) + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + cs.PublicId = id + + repo, err := s.staticRepoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, rowsUpdated, err = repo.UpdateCredentialStore(ctx, cs, item.GetVersion(), dbMask) + if err != nil { + return nil, errors.Wrap(ctx, 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) @@ -409,16 +503,34 @@ func (s Service) updateInRepo(ctx context.Context, projId, id string, mask []str 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 + var rows int + + switch subtypes.SubtypeFromId(domain, id) { + case vault.Subtype: + repo, err := s.vaultRepoFn() + 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(ctx, err, op, errors.WithMsg("unable to delete credential store")) + } + + case static.Subtype: + repo, err := s.staticRepoFn() + 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(ctx, err, op, errors.WithMsg("unable to delete credential store")) } - return false, errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete credential store")) } return rows > 0, nil } @@ -430,7 +542,12 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. res.Error = err return res } - repo, err := s.repoFn() + vaultRepo, err := s.vaultRepoFn() + if err != nil { + res.Error = err + return res + } + staticRepo, err := s.staticRepoFn() if err != nil { res.Error = err return res @@ -451,16 +568,32 @@ func (s Service) authResult(ctx context.Context, id string, a action.Type) auth. return res } default: - cs, err := repo.LookupCredentialStore(ctx, id) - if err != nil { - res.Error = err - return res + switch subtypes.SubtypeFromId(domain, id) { + case vault.Subtype: + cs, err := vaultRepo.LookupCredentialStore(ctx, id) + if err != nil { + res.Error = err + return res + } + if cs == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cs.GetScopeId() + + case static.Subtype: + var err error + cs, err := staticRepo.LookupCredentialStore(ctx, id) + if err != nil { + res.Error = err + return res + } + if cs == nil { + res.Error = handlers.NotFoundError() + return res + } + parentId = cs.GetScopeId() } - if cs == nil { - res.Error = handlers.NotFoundError() - return res - } - parentId = cs.GetScopeId() opts = append(opts, auth.WithId(id)) } opts = append(opts, auth.WithScopeId(parentId)) @@ -550,6 +683,23 @@ func toProto(in credential.Store, opt ...handlers.Option) (*pb.CredentialStore, return &out, nil } +func toStorageStaticStore(scopeId string, in *pb.CredentialStore) (out *static.CredentialStore, err error) { + const op = "credentialstores.toStorageStaticStore" + var opts []static.Option + if in.GetName() != nil { + opts = append(opts, static.WithName(in.GetName().GetValue())) + } + if in.GetDescription() != nil { + opts = append(opts, static.WithDescription(in.GetDescription().GetValue())) + } + + cs, err := static.NewCredentialStore(scopeId, opts...) + if err != nil { + return nil, errors.WrapDeprecated(err, op, errors.WithMsg("unable to build credential store for creation")) + } + return cs, err +} + func toStorageVaultStore(scopeId string, in *pb.CredentialStore) (out *vault.CredentialStore, err error) { const op = "credentialstores.toStorageVaultStore" var opts []vault.Option @@ -609,7 +759,7 @@ func toStorageVaultStore(scopeId string, in *pb.CredentialStore) (out *vault.Cre // * 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) + return handlers.ValidateGetRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix, static.CredentialStorePrefix) } func validateCreateRequest(req *pbs.CreateCredentialStoreRequest) error { @@ -651,6 +801,8 @@ func validateCreateRequest(req *pbs.CreateCredentialStoreRequest) error { if len(cs) > 0 && pk == nil { badFields[clientCertField] = "Cannot set a client certificate without a private key." } + case static.Subtype: + // No additional validation required for static credential store default: badFields[globals.TypeField] = "This is a required field and must be a known credential store type." } @@ -693,11 +845,11 @@ func validateUpdateRequest(req *pbs.UpdateCredentialStoreRequest) error { } } return badFields - }, vault.CredentialStorePrefix) + }, vault.CredentialStorePrefix, static.CredentialStorePrefix) } func validateDeleteRequest(req *pbs.DeleteCredentialStoreRequest) error { - return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix) + return handlers.ValidateDeleteRequest(handlers.NoopValidatorFn, req, vault.CredentialStorePrefix, static.CredentialStorePrefix) } func validateListRequest(req *pbs.ListCredentialStoresRequest) error { @@ -714,3 +866,20 @@ func validateListRequest(req *pbs.ListCredentialStoresRequest) error { } return nil } + +func calculateAuthorizedCollectionActions(ctx context.Context, authResults auth.VerifyResults, id string) (map[string]*structpb.ListValue, error) { + var collectionActions map[string]*structpb.ListValue + var err error + switch subtypes.SubtypeFromId(domain, id) { + case vault.Subtype: + collectionActions, err = auth.CalculateAuthorizedCollectionActions(ctx, authResults, vaultCollectionTypeMap, authResults.Scope.Id, id) + + case static.Subtype: + collectionActions, err = auth.CalculateAuthorizedCollectionActions(ctx, authResults, staticCollectionTypeMap, authResults.Scope.Id, id) + } + if err != nil { + return nil, err + } + + return collectionActions, nil +} diff --git a/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go b/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go index 50ba67293..3e5371c48 100644 --- a/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go +++ b/internal/daemon/controller/handlers/credentialstores/credentialstore_service_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" @@ -36,8 +37,8 @@ import ( ) var ( - testAuthorizedActions = []string{"no-op", "read", "update", "delete"} - testAuthorizedCollectionActions = map[string]*structpb.ListValue{ + testAuthorizedActions = []string{"no-op", "read", "update", "delete"} + testAuthorizedVaultCollectionActions = map[string]*structpb.ListValue{ "credential-libraries": { Values: []*structpb.Value{ structpb.NewStringValue("create"), @@ -45,6 +46,14 @@ var ( }, }, } + testAuthorizedStaticCollectionActions = map[string]*structpb.ListValue{ + "credentials": { + Values: []*structpb.Value{ + structpb.NewStringValue("create"), + structpb.NewStringValue("list"), + }, + }, + } ) func TestList(t *testing.T) { @@ -60,9 +69,12 @@ func TestList(t *testing.T) { iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - repoFn := func() (*vault.Repository, error) { + vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } _, prjNoStores := iam.TestScopes(t, iamRepo) _, prj := iam.TestScopes(t, iamRepo) @@ -78,7 +90,7 @@ func TestList(t *testing.T) { Version: s.GetVersion(), Type: vault.Subtype.String(), AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedCollectionActions, + AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{ VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{ Address: wrapperspb.String(s.GetVaultAddress()), @@ -91,6 +103,20 @@ func TestList(t *testing.T) { }) } + for _, s := range credstatic.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: credstatic.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, + }) + } + cases := []struct { name string req *pbs.ListCredentialStoresRequest @@ -111,11 +137,17 @@ func TestList(t *testing.T) { anonRes: &pbs.ListCredentialStoresResponse{}, }, { - name: "Filter to One Store", + name: "Filter to One Vault Store", req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[1].GetId())}, res: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]}, anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[1:2]}, }, + { + name: "Filter to One static Store", + req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/id"==%q`, wantStores[11].GetId())}, + res: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]}, + anonRes: &pbs.ListCredentialStoresResponse{Items: wantStores[11:12]}, + }, { name: "Filter on Attribute", req: &pbs.ListCredentialStoresRequest{ScopeId: prj.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/address"==%q`, wantStores[2].GetVaultCredentialStoreAttributes().GetAddress().Value)}, @@ -136,7 +168,7 @@ func TestList(t *testing.T) { } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - s, err := NewService(repoFn, iamRepoFn) + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) require.NoError(t, err, "Couldn't create new host set service.") // Test non-anonymous listing @@ -164,7 +196,7 @@ func TestList(t *testing.T) { } } -func TestCreate(t *testing.T) { +func TestCreateVault(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) @@ -177,9 +209,12 @@ func TestCreate(t *testing.T) { iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - repoFn := func() (*vault.Repository, error) { + vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } _, prj := iam.TestScopes(t, iamRepo) defaultCreated := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0].GetCreateTime().GetTimestamp() @@ -343,22 +378,6 @@ func TestCreate(t *testing.T) { 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: vault.Subtype.String(), - Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{ - VaultCredentialStoreAttributes: &pb.VaultCredentialStoreAttributes{ - Address: wrapperspb.String(v.Addr), - Token: wrapperspb.String(newToken()), - }, - }, - }}, - res: nil, - err: handlers.ApiErrorWithCode(codes.InvalidArgument), - }, { name: "Must specify type", req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ @@ -433,7 +452,7 @@ func TestCreate(t *testing.T) { }, }, AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedCollectionActions, + AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, }, }, }, @@ -474,7 +493,7 @@ func TestCreate(t *testing.T) { }, }, AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedCollectionActions, + AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, }, }, }, @@ -483,7 +502,7 @@ func TestCreate(t *testing.T) { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - s, err := NewService(repoFn, iamRepoFn) + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) require.NoError(err, "Error when getting new credential store service.") defer cleanup(s) @@ -533,6 +552,171 @@ func TestCreate(t *testing.T) { } } +func TestCreateStatic(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 + } + vaultRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } + + _, prj := iam.TestScopes(t, iamRepo) + defaultCreated := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + + 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) + } + } + + cases := []struct { + name string + req *pbs.CreateCredentialStoreRequest + res *pbs.CreateCredentialStoreResponse + idPrefix string + err error + wantErr bool + }{ + { + name: "Can't specify Id", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Id: credstatic.CredentialStorePrefix + "_notallowed", + Type: credstatic.Subtype.String(), + }}, + 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: credstatic.Subtype.String(), + }}, + 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: credstatic.Subtype.String(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Must specify type", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Create a valid static CredentialStore", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Type: credstatic.Subtype.String(), + }}, + idPrefix: credstatic.CredentialStorePrefix + "_", + res: &pbs.CreateCredentialStoreResponse{ + Uri: fmt.Sprintf("credential-stores/%s_", credstatic.CredentialStorePrefix), + Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credstatic.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, + }, + }, + }, + { + name: "Create a valid static CredentialStore with name and description", + req: &pbs.CreateCredentialStoreRequest{Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + Type: credstatic.Subtype.String(), + }}, + idPrefix: credstatic.CredentialStorePrefix + "_", + res: &pbs.CreateCredentialStoreResponse{ + Uri: fmt.Sprintf("credential-stores/%s_", credstatic.CredentialStorePrefix), + Item: &pb.CredentialStore{ + ScopeId: prj.GetPublicId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: credstatic.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + Name: &wrapperspb.StringValue{Value: "name"}, + Description: &wrapperspb.StringValue{Value: "desc"}, + AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, + }, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + s, err := NewService(vaultRepoFn, staticRepoFn, 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) + } + cmpOptions := []cmp.Option{ + protocmp.Transform(), + protocmp.SortRepeatedFields(got), + } + if got != nil { + assert.Contains(got.GetUri(), tc.res.Uri) + cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pbs.CreateCredentialStoreResponse{}, "uri")) + + assert.True(strings.HasPrefix(got.GetItem().GetId(), tc.idPrefix)) + cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "id")) + + 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.CreateTime.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.CreateTime.AsTime()), "New credential store should have been updated after default credential store. Was updated %v, which is after %v", gotUpdateTime, defaultCreated) + cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "created_time")) + cmpOptions = append(cmpOptions, protocmp.IgnoreFields(&pb.CredentialStore{}, "updated_time")) + } + assert.Empty(cmp.Diff(got, tc.res, cmpOptions...), "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) @@ -544,14 +728,18 @@ func TestGet(t *testing.T) { iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - repoFn := func() (*vault.Repository, error) { + vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } _, prj := iam.TestScopes(t, iamRepo) - store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] - s, err := NewService(repoFn, iamRepoFn) + vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] + staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) require.NoError(t, err) cases := []struct { @@ -561,35 +749,57 @@ func TestGet(t *testing.T) { err error }{ { - name: "success", - id: store.GetPublicId(), + name: "vault success", + id: vaultStore.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()}, + Id: vaultStore.GetPublicId(), + ScopeId: vaultStore.GetScopeId(), + Scope: &scopepb.ScopeInfo{Id: vaultStore.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, Type: vault.Subtype.String(), AuthorizedActions: testAuthorizedActions, - AuthorizedCollectionActions: testAuthorizedCollectionActions, - CreatedTime: store.CreateTime.GetTimestamp(), - UpdatedTime: store.UpdateTime.GetTimestamp(), + AuthorizedCollectionActions: testAuthorizedVaultCollectionActions, + CreatedTime: vaultStore.CreateTime.GetTimestamp(), + UpdatedTime: vaultStore.UpdateTime.GetTimestamp(), Version: 1, Attrs: &pb.CredentialStore_VaultCredentialStoreAttributes{ VaultCredentialStoreAttributes: &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()), + Address: wrapperspb.String(vaultStore.GetVaultAddress()), + TokenHmac: base64.RawURLEncoding.EncodeToString(vaultStore.Token().GetTokenHmac()), + ClientCertificate: wrapperspb.String(string(vaultStore.ClientCertificate().GetCertificate())), + ClientCertificateKeyHmac: base64.RawURLEncoding.EncodeToString(vaultStore.ClientCertificate().GetCertificateKeyHmac()), }, }, }, }, }, { - name: "not found error", + name: "static success", + id: staticStore.GetPublicId(), + res: &pbs.GetCredentialStoreResponse{ + Item: &pb.CredentialStore{ + Id: staticStore.GetPublicId(), + ScopeId: staticStore.GetScopeId(), + Scope: &scopepb.ScopeInfo{Id: staticStore.GetScopeId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: credstatic.Subtype.String(), + AuthorizedActions: testAuthorizedActions, + AuthorizedCollectionActions: testAuthorizedStaticCollectionActions, + CreatedTime: staticStore.CreateTime.GetTimestamp(), + UpdatedTime: staticStore.UpdateTime.GetTimestamp(), + Version: 1, + }, + }, + }, + { + name: "vault not found error", id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix), err: handlers.NotFoundError(), }, + { + name: "static not found error", + id: fmt.Sprintf("%s_1234567890", credstatic.CredentialStorePrefix), + err: handlers.NotFoundError(), + }, { name: "bad prefix", id: fmt.Sprintf("%s_1234567890", static.HostPrefix), @@ -632,14 +842,18 @@ func TestDelete(t *testing.T) { iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - repoFn := func() (*vault.Repository, error) { + vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } _, prj := iam.TestScopes(t, iamRepo) - store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0] - s, err := NewService(repoFn, iamRepoFn) + vaultStore := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2)[0] + staticStore := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) require.NoError(t, err) cases := []struct { @@ -649,14 +863,23 @@ func TestDelete(t *testing.T) { res *pbs.DeleteCredentialStoreResponse }{ { - name: "success", - id: store.GetPublicId(), + name: "vault success", + id: vaultStore.GetPublicId(), }, { - name: "not found error", + name: "static success", + id: staticStore.GetPublicId(), + }, + { + name: "vault not found error", id: fmt.Sprintf("%s_1234567890", vault.CredentialStorePrefix), err: handlers.NotFoundError(), }, + { + name: "static not found error", + id: fmt.Sprintf("%s_1234567890", credstatic.CredentialStorePrefix), + err: handlers.NotFoundError(), + }, { name: "bad prefix", id: fmt.Sprintf("%s_1234567890", static.HostPrefix), @@ -680,7 +903,7 @@ func TestDelete(t *testing.T) { } } -func TestUpdate(t *testing.T) { +func TestUpdateVault(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) @@ -693,14 +916,17 @@ func TestUpdate(t *testing.T) { iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } - repoFn := func() (*vault.Repository, error) { + vaultRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(rw, rw, kms, sche) } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } _, prj := iam.TestScopes(t, iamRepo) ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) - s, err := NewService(repoFn, iamRepoFn) + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) require.NoError(t, err) fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { @@ -1010,3 +1236,178 @@ func TestUpdate(t *testing.T) { }) } } + +func TestUpdateStatic(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 + } + vaultRepoFn := func() (*vault.Repository, error) { + return vault.NewRepository(rw, rw, kms, sche) + } + staticRepoFn := func() (*credstatic.Repository, error) { + return credstatic.NewRepository(context.Background(), rw, rw, kms) + } + + _, prj := iam.TestScopes(t, iamRepo) + ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) + + s, err := NewService(vaultRepoFn, staticRepoFn, iamRepoFn) + require.NoError(t, err) + + fieldmask := func(paths ...string) *fieldmaskpb.FieldMask { + return &fieldmaskpb.FieldMask{Paths: paths} + } + + freshStore := func() (*credstatic.CredentialStore, func()) { + t.Helper() + st := credstatic.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) + 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", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("name"), + Item: &pb.CredentialStore{ + Name: wrapperspb.String("new-name"), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Name = wrapperspb.String("new-name") + return out + }, + }, + { + name: "description", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("description"), + Item: &pb.CredentialStore{ + Description: wrapperspb.String("new-description"), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Description = wrapperspb.String("new-description") + return out + }, + }, + { + name: "name-and-description", + req: &pbs.UpdateCredentialStoreRequest{ + UpdateMask: fieldmask("name", "description"), + Item: &pb.CredentialStore{ + Name: wrapperspb.String("new-name"), + Description: wrapperspb.String("new-description"), + }, + }, + res: func(in *pb.CredentialStore) *pb.CredentialStore { + out := proto.Clone(in).(*pb.CredentialStore) + out.Name = wrapperspb.String("new-name") + out.Description = wrapperspb.String("new-description") + 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 + + 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")}, + }, + } + 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/gen/controller.swagger.json b/internal/gen/controller.swagger.json index 5c1718c85..1da680fc7 100644 --- a/internal/gen/controller.swagger.json +++ b/internal/gen/controller.swagger.json @@ -20,6 +20,9 @@ { "name": "controller.api.services.v1.CredentialLibraryService" }, + { + "name": "controller.api.services.v1.CredentialService" + }, { "name": "controller.api.services.v1.CredentialStoreService" }, @@ -908,6 +911,147 @@ ] } }, + "/v1/credentials": { + "get": { + "summary": "Lists all Credentials.", + "operationId": "CredentialService_ListCredentials", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.ListCredentialsResponse" + } + } + }, + "parameters": [ + { + "name": "credential_store_id", + "in": "query", + "required": false, + "type": "string" + }, + { + "name": "filter", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialService" + ] + }, + "post": { + "summary": "Creates a single Credential.", + "operationId": "CredentialService_CreateCredential", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, + "parameters": [ + { + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + ], + "tags": [ + "controller.api.services.v1.CredentialService" + ] + } + }, + "/v1/credentials/{id}": { + "get": { + "summary": "Gets a single Credential.", + "operationId": "CredentialService_GetCredential", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialService" + ] + }, + "delete": { + "summary": "Deletes a Credential", + "operationId": "CredentialService_DeleteCredential", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/controller.api.services.v1.DeleteCredentialResponse" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialService" + ] + }, + "patch": { + "summary": "Updates a Credential.", + "operationId": "CredentialService_UpdateCredential", + "responses": { + "200": { + "description": "", + "schema": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string" + }, + { + "name": "item", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + }, + { + "name": "update_mask", + "in": "query", + "required": false, + "type": "string" + } + ], + "tags": [ + "controller.api.services.v1.CredentialService" + ] + } + }, "/v1/groups": { "get": { "summary": "Lists all Groups.", @@ -3946,6 +4090,67 @@ }, "title": "CredentialLibrary contains all fields related to an Credential Library resource" }, + "controller.api.resources.credentials.v1.Credential": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "Output only. The ID of the Credential.", + "readOnly": true + }, + "credential_store_id": { + "type": "string", + "description": "The ID of the Credential Store of which this Credential is a part." + }, + "scope": { + "$ref": "#/definitions/controller.api.resources.scopes.v1.ScopeInfo", + "description": "Output only. Scope information for this Credential.", + "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 type." + }, + "attributes": { + "type": "object", + "description": "The attributes that are applicable for the specific Credential type." + }, + "authorized_actions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Output only. The available actions on this resource for this user.", + "readOnly": true + } + }, + "title": "Credential contains all fields related to an Credential resource" + }, "controller.api.resources.credentialstores.v1.CredentialStore": { "type": "object", "properties": { @@ -5524,6 +5729,17 @@ } } }, + "controller.api.services.v1.CreateCredentialResponse": { + "type": "object", + "properties": { + "uri": { + "type": "string" + }, + "item": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, "controller.api.services.v1.CreateCredentialStoreResponse": { "type": "object", "properties": { @@ -5646,6 +5862,9 @@ "controller.api.services.v1.DeleteCredentialLibraryResponse": { "type": "object" }, + "controller.api.services.v1.DeleteCredentialResponse": { + "type": "object" + }, "controller.api.services.v1.DeleteCredentialStoreResponse": { "type": "object" }, @@ -5708,6 +5927,14 @@ } } }, + "controller.api.services.v1.GetCredentialResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, "controller.api.services.v1.GetCredentialStoreResponse": { "type": "object", "properties": { @@ -5851,6 +6078,17 @@ } } }, + "controller.api.services.v1.ListCredentialsResponse": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + } + }, "controller.api.services.v1.ListGroupsResponse": { "type": "object", "properties": { @@ -6137,6 +6375,14 @@ } } }, + "controller.api.services.v1.UpdateCredentialResponse": { + "type": "object", + "properties": { + "item": { + "$ref": "#/definitions/controller.api.resources.credentials.v1.Credential" + } + } + }, "controller.api.services.v1.UpdateCredentialStoreResponse": { "type": "object", "properties": { diff --git a/internal/gen/controller/api/services/credential_service.pb.go b/internal/gen/controller/api/services/credential_service.pb.go new file mode 100644 index 000000000..2428ad687 --- /dev/null +++ b/internal/gen/controller/api/services/credential_service.pb.go @@ -0,0 +1,864 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc (unknown) +// source: controller/api/services/v1/credential_service.proto + +package services + +import ( + _ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options" + credentials "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + _ "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 GetCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *GetCredentialRequest) Reset() { + *x = GetCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialRequest) ProtoMessage() {} + +func (x *GetCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 GetCredentialRequest.ProtoReflect.Descriptor instead. +func (*GetCredentialRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{0} +} + +func (x *GetCredentialRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type GetCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentials.Credential `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *GetCredentialResponse) Reset() { + *x = GetCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetCredentialResponse) ProtoMessage() {} + +func (x *GetCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 GetCredentialResponse.ProtoReflect.Descriptor instead. +func (*GetCredentialResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{1} +} + +func (x *GetCredentialResponse) GetItem() *credentials.Credential { + if x != nil { + return x.Item + } + return nil +} + +type ListCredentialsRequest 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" class:"public"` // @gotags: `class:"public"` + Filter string `protobuf:"bytes,30,opt,name=filter,proto3" json:"filter,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *ListCredentialsRequest) Reset() { + *x = ListCredentialsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialsRequest) ProtoMessage() {} + +func (x *ListCredentialsRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 ListCredentialsRequest.ProtoReflect.Descriptor instead. +func (*ListCredentialsRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{2} +} + +func (x *ListCredentialsRequest) GetCredentialStoreId() string { + if x != nil { + return x.CredentialStoreId + } + return "" +} + +func (x *ListCredentialsRequest) GetFilter() string { + if x != nil { + return x.Filter + } + return "" +} + +type ListCredentialsResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []*credentials.Credential `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *ListCredentialsResponse) Reset() { + *x = ListCredentialsResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListCredentialsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListCredentialsResponse) ProtoMessage() {} + +func (x *ListCredentialsResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 ListCredentialsResponse.ProtoReflect.Descriptor instead. +func (*ListCredentialsResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{3} +} + +func (x *ListCredentialsResponse) GetItems() []*credentials.Credential { + if x != nil { + return x.Items + } + return nil +} + +type CreateCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentials.Credential `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialRequest) Reset() { + *x = CreateCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialRequest) ProtoMessage() {} + +func (x *CreateCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 CreateCredentialRequest.ProtoReflect.Descriptor instead. +func (*CreateCredentialRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{4} +} + +func (x *CreateCredentialRequest) GetItem() *credentials.Credential { + if x != nil { + return x.Item + } + return nil +} + +type CreateCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Uri string `protobuf:"bytes,1,opt,name=uri,proto3" json:"uri,omitempty" class:"public"` // @gotags: `class:"public"` + Item *credentials.Credential `protobuf:"bytes,2,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CreateCredentialResponse) Reset() { + *x = CreateCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CreateCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateCredentialResponse) ProtoMessage() {} + +func (x *CreateCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 CreateCredentialResponse.ProtoReflect.Descriptor instead. +func (*CreateCredentialResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{5} +} + +func (x *CreateCredentialResponse) GetUri() string { + if x != nil { + return x.Uri + } + return "" +} + +func (x *CreateCredentialResponse) GetItem() *credentials.Credential { + if x != nil { + return x.Item + } + return nil +} + +type UpdateCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` + Item *credentials.Credential `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 *UpdateCredentialRequest) Reset() { + *x = UpdateCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialRequest) ProtoMessage() {} + +func (x *UpdateCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 UpdateCredentialRequest.ProtoReflect.Descriptor instead. +func (*UpdateCredentialRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{6} +} + +func (x *UpdateCredentialRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateCredentialRequest) GetItem() *credentials.Credential { + if x != nil { + return x.Item + } + return nil +} + +func (x *UpdateCredentialRequest) GetUpdateMask() *fieldmaskpb.FieldMask { + if x != nil { + return x.UpdateMask + } + return nil +} + +type UpdateCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item *credentials.Credential `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *UpdateCredentialResponse) Reset() { + *x = UpdateCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UpdateCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateCredentialResponse) ProtoMessage() {} + +func (x *UpdateCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 UpdateCredentialResponse.ProtoReflect.Descriptor instead. +func (*UpdateCredentialResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{7} +} + +func (x *UpdateCredentialResponse) GetItem() *credentials.Credential { + if x != nil { + return x.Item + } + return nil +} + +type DeleteCredentialRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *DeleteCredentialRequest) Reset() { + *x = DeleteCredentialRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialRequest) ProtoMessage() {} + +func (x *DeleteCredentialRequest) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 DeleteCredentialRequest.ProtoReflect.Descriptor instead. +func (*DeleteCredentialRequest) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{8} +} + +func (x *DeleteCredentialRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +type DeleteCredentialResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteCredentialResponse) Reset() { + *x = DeleteCredentialResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_services_v1_credential_service_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteCredentialResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteCredentialResponse) ProtoMessage() {} + +func (x *DeleteCredentialResponse) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_services_v1_credential_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 DeleteCredentialResponse.ProtoReflect.Descriptor instead. +func (*DeleteCredentialResponse) Descriptor() ([]byte, []int) { + return file_controller_api_services_v1_credential_service_proto_rawDescGZIP(), []int{9} +} + +var File_controller_api_services_v1_credential_service_proto protoreflect.FileDescriptor + +var file_controller_api_services_v1_credential_service_proto_rawDesc = []byte{ + 0x0a, 0x33, 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, 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, 0x38, 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, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 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, 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, 0x22, 0x26, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, + 0x60, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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, 0x2e, 0x76, 0x31, + 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x04, 0x69, 0x74, 0x65, + 0x6d, 0x22, 0x57, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 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, 0x64, 0x0a, 0x17, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x49, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 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, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x22, 0x62, 0x0a, 0x17, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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, + 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x22, 0x75, 0x0a, 0x18, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x47, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x33, 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, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0xb0, 0x01, 0x0a, 0x17, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x47, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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, 0x2e, 0x76, 0x31, 0x2e, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x63, + 0x0a, 0x18, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x47, 0x0a, 0x04, 0x69, 0x74, + 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x04, 0x69, + 0x74, 0x65, 0x6d, 0x22, 0x29, 0x0a, 0x17, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x22, 0x1a, + 0x0a, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbe, 0x07, 0x0a, 0x11, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x12, 0xb6, 0x01, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x12, 0x30, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, + 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x40, 0x92, 0x41, 0x1b, 0x12, 0x19, 0x47, 0x65, + 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x43, 0x72, 0x65, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1c, 0x12, 0x14, 0x2f, + 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2f, 0x7b, + 0x69, 0x64, 0x7d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x12, 0xae, 0x01, 0x0a, 0x0f, 0x4c, 0x69, + 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x32, 0x2e, + 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x61, + 0x70, 0x69, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4c, + 0x69, 0x73, 0x74, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x32, 0x92, 0x41, 0x18, 0x12, 0x16, 0x4c, 0x69, 0x73, + 0x74, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x73, 0x2e, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x12, 0xc3, 0x01, 0x0a, 0x10, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x43, 0x72, 0x65, + 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x44, 0x92, 0x41, 0x1e, 0x12, + 0x1c, 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, 0x2e, 0x82, 0xd3, 0xe4, + 0x93, 0x02, 0x1d, 0x22, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 0x73, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, 0x69, 0x74, 0x65, 0x6d, + 0x12, 0xc1, 0x01, 0x0a, 0x10, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x61, 0x6c, 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, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x22, 0x42, 0x92, 0x41, 0x17, 0x12, 0x15, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x20, 0x61, + 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x22, 0x32, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, + 0x61, 0x6c, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x3a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x62, 0x04, + 0x69, 0x74, 0x65, 0x6d, 0x12, 0xb4, 0x01, 0x0a, 0x10, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, + 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x72, 0x65, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x35, 0x92, 0x41, 0x16, 0x12, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x82, + 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x2a, 0x14, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x2f, 0x7b, 0x69, 0x64, 0x7d, 0x42, 0x5b, 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, 0xa2, 0xe3, 0x29, 0x0a, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_api_services_v1_credential_service_proto_rawDescOnce sync.Once + file_controller_api_services_v1_credential_service_proto_rawDescData = file_controller_api_services_v1_credential_service_proto_rawDesc +) + +func file_controller_api_services_v1_credential_service_proto_rawDescGZIP() []byte { + file_controller_api_services_v1_credential_service_proto_rawDescOnce.Do(func() { + file_controller_api_services_v1_credential_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_services_v1_credential_service_proto_rawDescData) + }) + return file_controller_api_services_v1_credential_service_proto_rawDescData +} + +var file_controller_api_services_v1_credential_service_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_controller_api_services_v1_credential_service_proto_goTypes = []interface{}{ + (*GetCredentialRequest)(nil), // 0: controller.api.services.v1.GetCredentialRequest + (*GetCredentialResponse)(nil), // 1: controller.api.services.v1.GetCredentialResponse + (*ListCredentialsRequest)(nil), // 2: controller.api.services.v1.ListCredentialsRequest + (*ListCredentialsResponse)(nil), // 3: controller.api.services.v1.ListCredentialsResponse + (*CreateCredentialRequest)(nil), // 4: controller.api.services.v1.CreateCredentialRequest + (*CreateCredentialResponse)(nil), // 5: controller.api.services.v1.CreateCredentialResponse + (*UpdateCredentialRequest)(nil), // 6: controller.api.services.v1.UpdateCredentialRequest + (*UpdateCredentialResponse)(nil), // 7: controller.api.services.v1.UpdateCredentialResponse + (*DeleteCredentialRequest)(nil), // 8: controller.api.services.v1.DeleteCredentialRequest + (*DeleteCredentialResponse)(nil), // 9: controller.api.services.v1.DeleteCredentialResponse + (*credentials.Credential)(nil), // 10: controller.api.resources.credentials.v1.Credential + (*fieldmaskpb.FieldMask)(nil), // 11: google.protobuf.FieldMask +} +var file_controller_api_services_v1_credential_service_proto_depIdxs = []int32{ + 10, // 0: controller.api.services.v1.GetCredentialResponse.item:type_name -> controller.api.resources.credentials.v1.Credential + 10, // 1: controller.api.services.v1.ListCredentialsResponse.items:type_name -> controller.api.resources.credentials.v1.Credential + 10, // 2: controller.api.services.v1.CreateCredentialRequest.item:type_name -> controller.api.resources.credentials.v1.Credential + 10, // 3: controller.api.services.v1.CreateCredentialResponse.item:type_name -> controller.api.resources.credentials.v1.Credential + 10, // 4: controller.api.services.v1.UpdateCredentialRequest.item:type_name -> controller.api.resources.credentials.v1.Credential + 11, // 5: controller.api.services.v1.UpdateCredentialRequest.update_mask:type_name -> google.protobuf.FieldMask + 10, // 6: controller.api.services.v1.UpdateCredentialResponse.item:type_name -> controller.api.resources.credentials.v1.Credential + 0, // 7: controller.api.services.v1.CredentialService.GetCredential:input_type -> controller.api.services.v1.GetCredentialRequest + 2, // 8: controller.api.services.v1.CredentialService.ListCredentials:input_type -> controller.api.services.v1.ListCredentialsRequest + 4, // 9: controller.api.services.v1.CredentialService.CreateCredential:input_type -> controller.api.services.v1.CreateCredentialRequest + 6, // 10: controller.api.services.v1.CredentialService.UpdateCredential:input_type -> controller.api.services.v1.UpdateCredentialRequest + 8, // 11: controller.api.services.v1.CredentialService.DeleteCredential:input_type -> controller.api.services.v1.DeleteCredentialRequest + 1, // 12: controller.api.services.v1.CredentialService.GetCredential:output_type -> controller.api.services.v1.GetCredentialResponse + 3, // 13: controller.api.services.v1.CredentialService.ListCredentials:output_type -> controller.api.services.v1.ListCredentialsResponse + 5, // 14: controller.api.services.v1.CredentialService.CreateCredential:output_type -> controller.api.services.v1.CreateCredentialResponse + 7, // 15: controller.api.services.v1.CredentialService.UpdateCredential:output_type -> controller.api.services.v1.UpdateCredentialResponse + 9, // 16: controller.api.services.v1.CredentialService.DeleteCredential:output_type -> controller.api.services.v1.DeleteCredentialResponse + 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_service_proto_init() } +func file_controller_api_services_v1_credential_service_proto_init() { + if File_controller_api_services_v1_credential_service_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_services_v1_credential_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialRequest); 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_service_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetCredentialResponse); 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_service_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialsRequest); 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_service_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListCredentialsResponse); 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_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialRequest); 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_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CreateCredentialResponse); 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_service_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialRequest); 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_service_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UpdateCredentialResponse); 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_service_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialRequest); 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_service_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteCredentialResponse); 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_service_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_controller_api_services_v1_credential_service_proto_goTypes, + DependencyIndexes: file_controller_api_services_v1_credential_service_proto_depIdxs, + MessageInfos: file_controller_api_services_v1_credential_service_proto_msgTypes, + }.Build() + File_controller_api_services_v1_credential_service_proto = out.File + file_controller_api_services_v1_credential_service_proto_rawDesc = nil + file_controller_api_services_v1_credential_service_proto_goTypes = nil + file_controller_api_services_v1_credential_service_proto_depIdxs = nil +} diff --git a/internal/gen/controller/api/services/credential_service.pb.gw.go b/internal/gen/controller/api/services/credential_service.pb.gw.go new file mode 100644 index 000000000..ae502c708 --- /dev/null +++ b/internal/gen/controller/api/services/credential_service.pb.gw.go @@ -0,0 +1,632 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: controller/api/services/v1/credential_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_CredentialService_GetCredential_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialRequest + 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.GetCredential(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialService_GetCredential_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetCredentialRequest + 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.GetCredential(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialService_ListCredentials_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_CredentialService_ListCredentials_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialsRequest + 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_CredentialService_ListCredentials_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ListCredentials(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialService_ListCredentials_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ListCredentialsRequest + 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_CredentialService_ListCredentials_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ListCredentials(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialService_CreateCredential_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialRequest + 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.CreateCredential(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialService_CreateCredential_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq CreateCredentialRequest + 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.CreateCredential(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_CredentialService_UpdateCredential_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_CredentialService_UpdateCredential_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialRequest + 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_CredentialService_UpdateCredential_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.UpdateCredential(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialService_UpdateCredential_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq UpdateCredentialRequest + 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_CredentialService_UpdateCredential_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.UpdateCredential(ctx, &protoReq) + return msg, metadata, err + +} + +func request_CredentialService_DeleteCredential_0(ctx context.Context, marshaler runtime.Marshaler, client CredentialServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialRequest + 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.DeleteCredential(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_CredentialService_DeleteCredential_0(ctx context.Context, marshaler runtime.Marshaler, server CredentialServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeleteCredentialRequest + 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.DeleteCredential(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterCredentialServiceHandlerServer registers the http handlers for service CredentialService to "mux". +// UnaryRPC :call CredentialServiceServer 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 RegisterCredentialServiceHandlerFromEndpoint instead. +func RegisterCredentialServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux, server CredentialServiceServer) error { + + mux.Handle("GET", pattern_CredentialService_GetCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/GetCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialService_GetCredential_0(ctx, 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_CredentialService_GetCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_GetCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialService_ListCredentials_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/ListCredentials", runtime.WithHTTPPathPattern("/v1/credentials")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialService_ListCredentials_0(ctx, 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_CredentialService_ListCredentials_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialService_CreateCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/CreateCredential", runtime.WithHTTPPathPattern("/v1/credentials")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialService_CreateCredential_0(ctx, 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_CredentialService_CreateCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_CreateCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialService_UpdateCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/UpdateCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialService_UpdateCredential_0(ctx, 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_CredentialService_UpdateCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_UpdateCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialService_DeleteCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/DeleteCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_CredentialService_DeleteCredential_0(ctx, 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_CredentialService_DeleteCredential_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterCredentialServiceHandlerFromEndpoint is same as RegisterCredentialServiceHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterCredentialServiceHandlerFromEndpoint(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 RegisterCredentialServiceHandler(ctx, mux, conn) +} + +// RegisterCredentialServiceHandler registers the http handlers for service CredentialService to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterCredentialServiceHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterCredentialServiceHandlerClient(ctx, mux, NewCredentialServiceClient(conn)) +} + +// RegisterCredentialServiceHandlerClient registers the http handlers for service CredentialService +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "CredentialServiceClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "CredentialServiceClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "CredentialServiceClient" to call the correct interceptors. +func RegisterCredentialServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux, client CredentialServiceClient) error { + + mux.Handle("GET", pattern_CredentialService_GetCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/GetCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialService_GetCredential_0(ctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialService_GetCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_GetCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_CredentialService_ListCredentials_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/ListCredentials", runtime.WithHTTPPathPattern("/v1/credentials")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialService_ListCredentials_0(ctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialService_ListCredentials_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_CredentialService_CreateCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/CreateCredential", runtime.WithHTTPPathPattern("/v1/credentials")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialService_CreateCredential_0(ctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialService_CreateCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_CreateCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PATCH", pattern_CredentialService_UpdateCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/UpdateCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialService_UpdateCredential_0(ctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialService_UpdateCredential_0(ctx, mux, outboundMarshaler, w, req, response_CredentialService_UpdateCredential_0{resp}, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_CredentialService_DeleteCredential_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + ctx, err = runtime.AnnotateContext(ctx, mux, req, "/controller.api.services.v1.CredentialService/DeleteCredential", runtime.WithHTTPPathPattern("/v1/credentials/{id}")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_CredentialService_DeleteCredential_0(ctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_CredentialService_DeleteCredential_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +type response_CredentialService_GetCredential_0 struct { + proto.Message +} + +func (m response_CredentialService_GetCredential_0) XXX_ResponseBody() interface{} { + response := m.Message.(*GetCredentialResponse) + return response.Item +} + +type response_CredentialService_CreateCredential_0 struct { + proto.Message +} + +func (m response_CredentialService_CreateCredential_0) XXX_ResponseBody() interface{} { + response := m.Message.(*CreateCredentialResponse) + return response.Item +} + +type response_CredentialService_UpdateCredential_0 struct { + proto.Message +} + +func (m response_CredentialService_UpdateCredential_0) XXX_ResponseBody() interface{} { + response := m.Message.(*UpdateCredentialResponse) + return response.Item +} + +var ( + pattern_CredentialService_GetCredential_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credentials", "id"}, "")) + + pattern_CredentialService_ListCredentials_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credentials"}, "")) + + pattern_CredentialService_CreateCredential_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "credentials"}, "")) + + pattern_CredentialService_UpdateCredential_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credentials", "id"}, "")) + + pattern_CredentialService_DeleteCredential_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "credentials", "id"}, "")) +) + +var ( + forward_CredentialService_GetCredential_0 = runtime.ForwardResponseMessage + + forward_CredentialService_ListCredentials_0 = runtime.ForwardResponseMessage + + forward_CredentialService_CreateCredential_0 = runtime.ForwardResponseMessage + + forward_CredentialService_UpdateCredential_0 = runtime.ForwardResponseMessage + + forward_CredentialService_DeleteCredential_0 = runtime.ForwardResponseMessage +) diff --git a/internal/gen/controller/api/services/credential_service_grpc.pb.go b/internal/gen/controller/api/services/credential_service_grpc.pb.go new file mode 100644 index 000000000..0fd4dfd59 --- /dev/null +++ b/internal/gen/controller/api/services/credential_service_grpc.pb.go @@ -0,0 +1,287 @@ +// 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 + +// CredentialServiceClient is the client API for CredentialService 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 CredentialServiceClient interface { + // GetCredential returns a stored Credential if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredential(ctx context.Context, in *GetCredentialRequest, opts ...grpc.CallOption) (*GetCredentialResponse, error) + // ListCredentials returns a list of stored Credentials which are in the + // provided store. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentials(ctx context.Context, in *ListCredentialsRequest, opts ...grpc.CallOption) (*ListCredentialsResponse, error) + // CreateCredential creates and stores an Credential in boundary. The + // provided request must include the credential_store_id in which the Credential + // will be created. If the store 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 in the same store, an error is returned. + CreateCredential(ctx context.Context, in *CreateCredentialRequest, opts ...grpc.CallOption) (*CreateCredentialResponse, error) + // UpdateCredential updates an existing Credential in boundary. The provided + // Credential 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 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 in the parent scope. + UpdateCredential(ctx context.Context, in *UpdateCredentialRequest, opts ...grpc.CallOption) (*UpdateCredentialResponse, error) + // DeleteCredential removes an Credential from Boundary. If the Credential id + // is malformed or not provided an error is returned. + DeleteCredential(ctx context.Context, in *DeleteCredentialRequest, opts ...grpc.CallOption) (*DeleteCredentialResponse, error) +} + +type credentialServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewCredentialServiceClient(cc grpc.ClientConnInterface) CredentialServiceClient { + return &credentialServiceClient{cc} +} + +func (c *credentialServiceClient) GetCredential(ctx context.Context, in *GetCredentialRequest, opts ...grpc.CallOption) (*GetCredentialResponse, error) { + out := new(GetCredentialResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialService/GetCredential", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) ListCredentials(ctx context.Context, in *ListCredentialsRequest, opts ...grpc.CallOption) (*ListCredentialsResponse, error) { + out := new(ListCredentialsResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialService/ListCredentials", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) CreateCredential(ctx context.Context, in *CreateCredentialRequest, opts ...grpc.CallOption) (*CreateCredentialResponse, error) { + out := new(CreateCredentialResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialService/CreateCredential", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) UpdateCredential(ctx context.Context, in *UpdateCredentialRequest, opts ...grpc.CallOption) (*UpdateCredentialResponse, error) { + out := new(UpdateCredentialResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialService/UpdateCredential", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *credentialServiceClient) DeleteCredential(ctx context.Context, in *DeleteCredentialRequest, opts ...grpc.CallOption) (*DeleteCredentialResponse, error) { + out := new(DeleteCredentialResponse) + err := c.cc.Invoke(ctx, "/controller.api.services.v1.CredentialService/DeleteCredential", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// CredentialServiceServer is the server API for CredentialService service. +// All implementations must embed UnimplementedCredentialServiceServer +// for forward compatibility +type CredentialServiceServer interface { + // GetCredential returns a stored Credential if present. The provided request + // must include the Credential Store id. If missing, malformed or referencing a + // non existing resource an error is returned. + GetCredential(context.Context, *GetCredentialRequest) (*GetCredentialResponse, error) + // ListCredentials returns a list of stored Credentials which are in the + // provided store. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + ListCredentials(context.Context, *ListCredentialsRequest) (*ListCredentialsResponse, error) + // CreateCredential creates and stores an Credential in boundary. The + // provided request must include the credential_store_id in which the Credential + // will be created. If the store 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 in the same store, an error is returned. + CreateCredential(context.Context, *CreateCredentialRequest) (*CreateCredentialResponse, error) + // UpdateCredential updates an existing Credential in boundary. The provided + // Credential 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 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 in the parent scope. + UpdateCredential(context.Context, *UpdateCredentialRequest) (*UpdateCredentialResponse, error) + // DeleteCredential removes an Credential from Boundary. If the Credential id + // is malformed or not provided an error is returned. + DeleteCredential(context.Context, *DeleteCredentialRequest) (*DeleteCredentialResponse, error) + mustEmbedUnimplementedCredentialServiceServer() +} + +// UnimplementedCredentialServiceServer must be embedded to have forward compatible implementations. +type UnimplementedCredentialServiceServer struct { +} + +func (UnimplementedCredentialServiceServer) GetCredential(context.Context, *GetCredentialRequest) (*GetCredentialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetCredential not implemented") +} +func (UnimplementedCredentialServiceServer) ListCredentials(context.Context, *ListCredentialsRequest) (*ListCredentialsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListCredentials not implemented") +} +func (UnimplementedCredentialServiceServer) CreateCredential(context.Context, *CreateCredentialRequest) (*CreateCredentialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateCredential not implemented") +} +func (UnimplementedCredentialServiceServer) UpdateCredential(context.Context, *UpdateCredentialRequest) (*UpdateCredentialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateCredential not implemented") +} +func (UnimplementedCredentialServiceServer) DeleteCredential(context.Context, *DeleteCredentialRequest) (*DeleteCredentialResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteCredential not implemented") +} +func (UnimplementedCredentialServiceServer) mustEmbedUnimplementedCredentialServiceServer() {} + +// UnsafeCredentialServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to CredentialServiceServer will +// result in compilation errors. +type UnsafeCredentialServiceServer interface { + mustEmbedUnimplementedCredentialServiceServer() +} + +func RegisterCredentialServiceServer(s grpc.ServiceRegistrar, srv CredentialServiceServer) { + s.RegisterService(&CredentialService_ServiceDesc, srv) +} + +func _CredentialService_GetCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).GetCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialService/GetCredential", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).GetCredential(ctx, req.(*GetCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_ListCredentials_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListCredentialsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).ListCredentials(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialService/ListCredentials", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).ListCredentials(ctx, req.(*ListCredentialsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_CreateCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).CreateCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialService/CreateCredential", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).CreateCredential(ctx, req.(*CreateCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_UpdateCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).UpdateCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialService/UpdateCredential", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).UpdateCredential(ctx, req.(*UpdateCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _CredentialService_DeleteCredential_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteCredentialRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CredentialServiceServer).DeleteCredential(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/controller.api.services.v1.CredentialService/DeleteCredential", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CredentialServiceServer).DeleteCredential(ctx, req.(*DeleteCredentialRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// CredentialService_ServiceDesc is the grpc.ServiceDesc for CredentialService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var CredentialService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "controller.api.services.v1.CredentialService", + HandlerType: (*CredentialServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCredential", + Handler: _CredentialService_GetCredential_Handler, + }, + { + MethodName: "ListCredentials", + Handler: _CredentialService_ListCredentials_Handler, + }, + { + MethodName: "CreateCredential", + Handler: _CredentialService_CreateCredential_Handler, + }, + { + MethodName: "UpdateCredential", + Handler: _CredentialService_UpdateCredential_Handler, + }, + { + MethodName: "DeleteCredential", + Handler: _CredentialService_DeleteCredential_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "controller/api/services/v1/credential_service.proto", +} diff --git a/internal/perms/grants_test.go b/internal/perms/grants_test.go index 19f807280..60fff56a3 100644 --- a/internal/perms/grants_test.go +++ b/internal/perms/grants_test.go @@ -108,7 +108,7 @@ func Test_ActionParsingValidation(t *testing.T) { func Test_ValidateType(t *testing.T) { t.Parallel() var g Grant - for i := resource.Unknown; i <= resource.CredentialLibrary; i++ { + for i := resource.Unknown; i <= resource.Credential; i++ { g.typ = i if i == resource.Controller || i == resource.Worker { assert.Error(t, g.validateType()) diff --git a/internal/proto/controller/api/resources/credentials/v1/credential.proto b/internal/proto/controller/api/resources/credentials/v1/credential.proto new file mode 100644 index 000000000..7b4bd58ec --- /dev/null +++ b/internal/proto/controller/api/resources/credentials/v1/credential.proto @@ -0,0 +1,102 @@ +syntax = "proto3"; + +package controller.api.resources.credentials.v1; + +import "controller/api/resources/scopes/v1/scope.proto"; +import "controller/custom_options/v1/options.proto"; +import "google/api/visibility.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +option go_package = "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials;credentials"; + +// Credential contains all fields related to an Credential resource +message Credential { + // Output only. The ID of the Credential. + string id = 10; // @gotags: `class:"public"` + + // The ID of the Credential Store of which this Credential is a part. + string credential_store_id = 20 [json_name = "credential_store_id"]; // @gotags: `class:"public"` + + // Output only. Scope information for this Credential. + 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" + } + ]; // @gotags: `class:"public"` + + // 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" + } + ]; // @gotags: `class:"public"` + + // Output only. The time this resource was created. + google.protobuf.Timestamp created_time = 60 [json_name = "created_time"]; // @gotags: `class:"public"` + + // Output only. The time this resource was last updated. + google.protobuf.Timestamp updated_time = 70 [json_name = "updated_time"]; // @gotags: `class:"public"` + + // 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; // @gotags: `class:"public"` + + // The Credential type. + string type = 90; // @gotags: `class:"public"` + + oneof attrs { + // The attributes that are applicable for the specific Credential type. + google.protobuf.Struct attributes = 100 [ + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.subtype) = "default" + ]; + UsernamePasswordAttributes username_password_attributes = 101 [ + (google.api.field_visibility).restriction = "INTERNAL", + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.subtype) = "username_password" + ]; + } + + // Output only. The available actions on this resource for this user. + repeated string authorized_actions = 300 [json_name = "authorized_actions"]; // @gotags: `class:"public"` +} + +// The attributes of a UsernamePassword Credential. +message UsernamePasswordAttributes { + // The username associated with the credential. + google.protobuf.StringValue username = 10 [ + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.mask_mapping) = { + this: "attributes.username" + that: "Username" + } + ]; // @gotags: `class:"public"` + + // Input only. The password associated with the credential. + google.protobuf.StringValue password = 20 [ + json_name = "password", + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.mask_mapping) = { + this: "attributes.password" + that: "Password" + } + ]; // @gotags: `class:"secret"` + + // Output only. The hmac value of the password. + string password_hmac = 30 [ + json_name = "password_hmac", + (custom_options.v1.mask_mapping) = { + this: "attributes.password_hmac" + that: "PasswordHmac" + } + ]; // @gotags: `class:"public"` +} diff --git a/internal/proto/controller/api/services/v1/credential_service.proto b/internal/proto/controller/api/services/v1/credential_service.proto new file mode 100644 index 000000000..f3cc24611 --- /dev/null +++ b/internal/proto/controller/api/services/v1/credential_service.proto @@ -0,0 +1,127 @@ +syntax = "proto3"; + +package controller.api.services.v1; + +import "controller/api/resources/credentials/v1/credential.proto"; +import "controller/custom_options/v1/options.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/field_mask.proto"; +import "protoc-gen-openapiv2/options/annotations.proto"; + +option go_package = "github.com/hashicorp/boundary/internal/gen/controller/api/services;services"; +option (custom_options.v1.domain) = "credential"; + +service CredentialService { + // GetCredential returns a stored Credential 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 GetCredential(GetCredentialRequest) returns (GetCredentialResponse) { + option (google.api.http) = { + get: "/v1/credentials/{id}" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Gets a single Credential." + }; + } + + // ListCredentials returns a list of stored Credentials which are in the + // provided store. The request must include the Credential Store id and if missing, + // malformed, or referencing a non existing scope, an error is returned. + rpc ListCredentials(ListCredentialsRequest) returns (ListCredentialsResponse) { + option (google.api.http) = { + get: "/v1/credentials" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Lists all Credentials." + }; + } + + // CreateCredential creates and stores an Credential in boundary. The + // provided request must include the credential_store_id in which the Credential + // will be created. If the store 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 in the same store, an error is returned. + rpc CreateCredential(CreateCredentialRequest) returns (CreateCredentialResponse) { + option (google.api.http) = { + post: "/v1/credentials" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Creates a single Credential." + }; + } + + // UpdateCredential updates an existing Credential in boundary. The provided + // Credential 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 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 in the parent scope. + rpc UpdateCredential(UpdateCredentialRequest) returns (UpdateCredentialResponse) { + option (google.api.http) = { + patch: "/v1/credentials/{id}" + body: "item" + response_body: "item" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Updates a Credential." + }; + } + + // DeleteCredential removes an Credential from Boundary. If the Credential id + // is malformed or not provided an error is returned. + rpc DeleteCredential(DeleteCredentialRequest) returns (DeleteCredentialResponse) { + option (google.api.http) = { + delete: "/v1/credentials/{id}" + }; + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + summary: "Deletes a Credential" + }; + } +} + +message GetCredentialRequest { + string id = 1; // @gotags: `class:"public"` +} + +message GetCredentialResponse { + resources.credentials.v1.Credential item = 1; +} + +message ListCredentialsRequest { + string credential_store_id = 1 [json_name = "scope_id"]; // @gotags: `class:"public"` + string filter = 30 [json_name = "filter"]; // @gotags: `class:"public"` +} + +message ListCredentialsResponse { + repeated resources.credentials.v1.Credential items = 1; +} + +message CreateCredentialRequest { + resources.credentials.v1.Credential item = 1; +} + +message CreateCredentialResponse { + string uri = 1; // @gotags: `class:"public"` + resources.credentials.v1.Credential item = 2; +} + +message UpdateCredentialRequest { + string id = 1; // @gotags: `class:"public"` + resources.credentials.v1.Credential item = 2; + google.protobuf.FieldMask update_mask = 3 [json_name = "update_mask"]; +} + +message UpdateCredentialResponse { + resources.credentials.v1.Credential item = 1; +} + +message DeleteCredentialRequest { + string id = 1; // @gotags: `class:"public"` +} + +message DeleteCredentialResponse {} diff --git a/internal/proto/controller/storage/credential/static/store/v1/static.proto b/internal/proto/controller/storage/credential/static/store/v1/static.proto new file mode 100644 index 000000000..4a1116320 --- /dev/null +++ b/internal/proto/controller/storage/credential/static/store/v1/static.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; + +// Package store provides protobufs for storing types in the static +// credential package. +package controller.storage.credential.static.store.v1; + +import "controller/custom_options/v1/options.proto"; +import "controller/storage/timestamp/v1/timestamp.proto"; + +option go_package = "github.com/hashicorp/boundary/internal/credential/static/store;store"; + +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; + + // 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" + }]; + + // The scope_id of the owning scope. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string scope_id = 6; + + // version allows optimistic locking of the resource. + // @inject_tag: `gorm:"default:null"` + uint32 version = 7; +} + +message UsernamePasswordCredential { + // 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 static 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; + + // username is the username associated with the credential. + // It must be set. + // @inject_tag: `gorm:"not_null"` + string username = 8 [(custom_options.v1.mask_mapping) = { + this: "Username" + that: "attributes.username" + }]; + + // password is the plain-text of the password associated with the credential. We are + // not storing this plain-text password in the database. + // @inject_tag: `gorm:"-" wrapping:"pt,password_data"` + bytes password = 9 [(custom_options.v1.mask_mapping) = { + this: "Password" + that: "attributes.password" + }]; + + // ct_password is the ciphertext of the password. It + // is stored in the database. + // @inject_tag: `gorm:"column:password_encrypted;not_null" wrapping:"ct,password_data"` + bytes ct_password = 10; + + // password_hmac is a sha256-hmac of the unencrypted password. It is recalculated + // everytime the password is updated. + // @inject_tag: `gorm:"not_null"` + bytes password_hmac = 11 [(custom_options.v1.mask_mapping) = { + this: "PasswordHmac" + that: "attributes.password_hmac" + }]; + + // 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 = 12; +} diff --git a/internal/tests/api/credentials/credentials_classification_test.go b/internal/tests/api/credentials/credentials_classification_test.go new file mode 100644 index 000000000..ea981df7a --- /dev/null +++ b/internal/tests/api/credentials/credentials_classification_test.go @@ -0,0 +1,201 @@ +package credentials_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/hashicorp/boundary/sdk/pbs/controller/api" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" + "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + "github.com/hashicorp/boundary/sdk/wrapper" + "github.com/hashicorp/eventlogger" + "github.com/hashicorp/eventlogger/filters/encrypt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" + "google.golang.org/protobuf/types/known/wrapperspb" +) + +func TestCredentialClassification(t *testing.T) { + ctx := context.Background() + now := time.Now() + pbNow := timestamppb.Now() + wrapper := wrapper.TestWrapper(t) + testEncryptingFilter := api.NewEncryptFilter(t, wrapper) + + cases := []struct { + name string + in *eventlogger.Event + want *eventlogger.Event + }{ + { + "Usernamepassword", + &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.Credential{ + Id: "id", + CredentialStoreId: "cred-store-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-descriptione", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{ + Value: "name", + }, + Description: &wrapperspb.StringValue{ + Value: "description", + }, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 1, + Type: "username_password", + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: &wrapperspb.StringValue{Value: "username"}, + Password: &wrapperspb.StringValue{Value: "password"}, + PasswordHmac: "password-hmac", + }, + }, + AuthorizedActions: []string{ + "action-1", + "action-2", + }, + }, + }, + &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.Credential{ + Id: "id", + CredentialStoreId: "cred-store-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-descriptione", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{ + Value: "name", + }, + Description: &wrapperspb.StringValue{ + Value: "description", + }, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 1, + Type: "username_password", + Attrs: &pb.Credential_UsernamePasswordAttributes{ + UsernamePasswordAttributes: &pb.UsernamePasswordAttributes{ + Username: &wrapperspb.StringValue{Value: "username"}, + Password: &wrapperspb.StringValue{Value: "[REDACTED]"}, + PasswordHmac: "password-hmac", + }, + }, + AuthorizedActions: []string{ + "action-1", + "action-2", + }, + }, + }, + }, + { + "Default", + &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.Credential{ + Id: "id", + CredentialStoreId: "cred-store-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-descriptione", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{ + Value: "name", + }, + Description: &wrapperspb.StringValue{ + Value: "description", + }, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 1, + Type: "username_password", + Attrs: &pb.Credential_Attributes{ + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field1": structpb.NewStringValue("value-1"), + "field2": structpb.NewStringValue("value-2"), + }, + }, + }, + AuthorizedActions: []string{ + "action-1", + "action-2", + }, + }, + }, + &eventlogger.Event{ + Type: "test", + CreatedAt: now, + Payload: &pb.Credential{ + Id: "id", + CredentialStoreId: "cred-store-id", + Scope: &scopes.ScopeInfo{ + Id: "scope-id", + Type: "scope-type", + Name: "scope-name", + Description: "scope-descriptione", + ParentScopeId: "scope-parent-scope-id", + }, + Name: &wrapperspb.StringValue{ + Value: "name", + }, + Description: &wrapperspb.StringValue{ + Value: "description", + }, + CreatedTime: pbNow, + UpdatedTime: pbNow, + Version: 1, + Type: "username_password", + Attrs: &pb.Credential_Attributes{ + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "field1": structpb.NewStringValue(encrypt.RedactedData), + "field2": structpb.NewStringValue(encrypt.RedactedData), + }, + }, + }, + AuthorizedActions: []string{ + "action-1", + "action-2", + }, + }, + }, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + wantJSON, err := json.Marshal(tc.want) + require.NoError(t, err) + + got, err := testEncryptingFilter.Process(ctx, tc.in) + require.NoError(t, err) + require.NotNil(t, got) + gotJSON, err := json.Marshal(got) + require.NoError(t, err) + assert.JSONEq(t, string(wantJSON), string(gotJSON)) + }) + } +} diff --git a/internal/tests/api/credentials/credentials_test.go b/internal/tests/api/credentials/credentials_test.go new file mode 100644 index 000000000..4b41ad42c --- /dev/null +++ b/internal/tests/api/credentials/credentials_test.go @@ -0,0 +1,208 @@ +package credentials_test + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/credentials" + "github.com/hashicorp/boundary/api/credentialstores" + "github.com/hashicorp/boundary/internal/credential/static" + "github.com/hashicorp/boundary/internal/daemon/controller" + "github.com/hashicorp/boundary/internal/iam" + "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() + + 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(), "static", proj.GetPublicId()) + require.NoError(err) + require.NotNil(cs) + + credClient := credentials.NewClient(client) + + ul, err := credClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.Empty(ul.Items) + + cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, + credentials.WithName("0"), + credentials.WithDescription("description"), + credentials.WithUsernamePasswordCredentialUsername("user"), + credentials.WithUsernamePasswordCredentialPassword("pass")) + require.NoError(err) + + expected := make([]*credentials.Credential, 10) + expected[0] = cred.Item + + ul, err = credClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items)) + + for i := 1; i < 10; i++ { + cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, + credentials.WithName(fmt.Sprintf("%d", i)), + credentials.WithDescription("description"), + credentials.WithUsernamePasswordCredentialUsername("user"), + credentials.WithUsernamePasswordCredentialPassword("pass")) + require.NoError(err) + expected[i] = cred.Item + } + ul, err = credClient.List(tc.Context(), cs.Item.Id) + require.NoError(err) + assert.ElementsMatch(comparableCatalogSlice(expected), comparableCatalogSlice(ul.Items)) + + filterItem := ul.Items[3] + ul, err = credClient.List(tc.Context(), cs.Item.Id, + credentials.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 []*credentials.Credential) []credentials.Credential { + var filtered []credentials.Credential + for _, i := range in { + p := credentials.Credential{ + 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() + + 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(), "static", proj.GetPublicId()) + require.NoError(err) + require.NotNil(cs) + + checkResource := func(step string, c *credentials.Credential, wantedName, wantedUser string, wantVersion uint32) { + assert.NotNil(c, "returned no resource", step) + assert.Equal(wantedName, c.Name, step) + gotUser, ok := c.Attributes["username"] + require.True(ok) + assert.Equal(wantedUser, gotUser, step) + assert.Equal(wantVersion, c.Version) + } + credClient := credentials.NewClient(client) + + cred, err := credClient.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"), + credentials.WithUsernamePasswordCredentialUsername("user"), credentials.WithUsernamePasswordCredentialPassword("pass")) + require.NoError(err) + require.NotNil(cs) + checkResource("create", cred.Item, "foo", "user", 1) + + cred, err = credClient.Read(tc.Context(), cred.Item.Id) + require.NoError(err) + require.NotNil(cs) + checkResource("read", cred.Item, "foo", "user", 1) + + cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, credentials.WithName("bar")) + require.NoError(err) + require.NotNil(cs) + checkResource("update", cred.Item, "bar", "user", 2) + + cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, credentials.WithUsernamePasswordCredentialUsername("newuser")) + require.NoError(err) + require.NotNil(cs) + checkResource("update", cred.Item, "bar", "newuser", 3) + + cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, credentials.DefaultName()) + require.NoError(err) + require.NotNil(cs) + checkResource("update", cred.Item, "", "newuser", 4) + + cred, err = credClient.Update(tc.Context(), cred.Item.Id, cred.Item.Version, + credentials.WithName("newuser"), credentials.WithUsernamePasswordCredentialUsername("neweruser")) + require.NoError(err) + require.NotNil(cs) + checkResource("update", cred.Item, "newuser", "neweruser", 5) + + _, err = credClient.Delete(tc.Context(), cred.Item.Id) + assert.NoError(err) + + _, err = credClient.Delete(tc.Context(), cred.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() + + 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(), "static", proj.GetPublicId()) + require.NoError(err) + require.NotNil(cs) + + c := credentials.NewClient(client) + + cred, err := c.Create(tc.Context(), static.UsernamePasswordSubtype.String(), cs.Item.Id, credentials.WithName("foo"), + credentials.WithUsernamePasswordCredentialUsername("user"), credentials.WithUsernamePasswordCredentialPassword("pass")) + require.NoError(err) + require.NotNil(cred) + + // A malformed id is processed as the id and not a different path to the api. + _, err = c.Read(tc.Context(), fmt.Sprintf("%s/../", cred.Item.Id)) + require.Error(err) + apiErr := api.AsServerError(err) + require.NotNil(apiErr) + assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode()) + require.Len(apiErr.Details.RequestFields, 1) + assert.Equal(apiErr.Details.RequestFields[0].Name, "id") + + // Updating the wrong version should fail. + _, err = c.Update(tc.Context(), cred.Item.Id, 73, credentials.WithName("anything")) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode()) + + // Same name + _, err = c.Create(tc.Context(), static.UsernamePasswordSubtype.String(), proj.GetPublicId(), credentials.WithName("foo")) + require.Error(err) + apiErr = api.AsServerError(err) + assert.NotNil(apiErr) + + _, err = c.Read(tc.Context(), static.CredentialPrefix+"_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/credentialstores/credentialstore_test.go b/internal/tests/api/credentialstores/credentialstore_test.go index 158a69ace..dc5e077bf 100644 --- a/internal/tests/api/credentialstores/credentialstore_test.go +++ b/internal/tests/api/credentialstores/credentialstore_test.go @@ -49,12 +49,17 @@ func TestList(t *testing.T) { require.NoError(err) assert.ElementsMatch(comparableCatalogSlice(expected[:1]), comparableCatalogSlice(ul.Items)) - for i := 1; i < 10; i++ { + for i := 1; i < 5; 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 } + for i := 5; i < 10; i++ { + cr, err = storeClient.Create(tc.Context(), "static", proj.GetPublicId(), credentialstores.WithName(expected[i].Name)) + 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)) diff --git a/internal/tests/cli/boundary/_credential_stores.bash b/internal/tests/cli/boundary/_credential_stores.bash new file mode 100644 index 000000000..54db7cc93 --- /dev/null +++ b/internal/tests/cli/boundary/_credential_stores.bash @@ -0,0 +1,30 @@ +load _authorized_actions + +function create_static_credential_store() { + local name=$1 + local sid=$2 + + boundary credential-stores create static \ + -name $name \ + -description 'test static credential store' \ + -scope-id $sid +} + +function read_credential_store() { + boundary credential-stores read -id $1 -format json +} + +function delete_credential_store() { + boundary credential-stores delete -id $1 +} + +function list_credential_stores() { + boundary credential-stores list -scope-id $1 -format json +} + +function credential_store_id() { + local name=$1 + local sid=$2 + + strip $(list_credential_stores $sid | jq -c ".items[] | select(.name | contains(\"$name\")) | .[\"id\"]") +} diff --git a/internal/tests/cli/boundary/_credentials.bash b/internal/tests/cli/boundary/_credentials.bash new file mode 100644 index 000000000..016ab48b7 --- /dev/null +++ b/internal/tests/cli/boundary/_credentials.bash @@ -0,0 +1,34 @@ +load _authorized_actions + +function create_username_password_credential() { + local name=$1 + local sid=$2 + local user=$3 + local pass=$4 + + boundary credentials create username-password \ + -name $name \ + -description 'test username password credential' \ + -credential-store-id $sid \ + -username $user \ + -password $pass +} + +function read_credential() { + boundary credentials read -id $1 -format json +} + +function delete_credential() { + boundary credentials delete -id $1 +} + +function list_credentials() { + boundary credentials list -credential-store-id $1 -format json +} + +function credential_id() { + local name=$1 + local sid=$2 + + strip $(list_credentials $sid | jq -c ".items[] | select(.name | contains(\"$name\")) | .[\"id\"]") +} diff --git a/internal/tests/cli/boundary/credential_stores.bats b/internal/tests/cli/boundary/credential_stores.bats new file mode 100644 index 000000000..a2dbde63a --- /dev/null +++ b/internal/tests/cli/boundary/credential_stores.bats @@ -0,0 +1,47 @@ +#!/usr/bin/env bats + +load _auth +load _helpers +load _credential_stores + +export NEW_STORE='test' + +@test "boundary/login: can login as default user" { + run login $DEFAULT_LOGIN + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can create $NEW_STORE static store in default project" { + run create_static_credential_store $NEW_STORE $DEFAULT_P_ID + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can not create already created $NEW_STORE static store" { + run create_static_credential_store $NEW_STORE $DEFAULT_P_ID + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credential-stores: can read $NEW_STORE static store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run read_credential_store $csid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can delete $NEW_STORE static store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run delete_credential_store $csid + echo "$output" + run has_status_code "$output" "204" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can not read deleted $NEW_STORE static store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run read_credential_store $csid + echo "$output" + [ "$status" -eq 1 ] +} diff --git a/internal/tests/cli/boundary/credentials.bats b/internal/tests/cli/boundary/credentials.bats new file mode 100644 index 000000000..8878f0437 --- /dev/null +++ b/internal/tests/cli/boundary/credentials.bats @@ -0,0 +1,60 @@ +#!/usr/bin/env bats + +load _auth +load _helpers +load _credential_stores +load _credentials + +export NEW_STORE='credentials-test-store' +export NEW_CREDENTIAL='test' + +@test "boundary/login: can login as default user" { + run login $DEFAULT_LOGIN + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can create $NEW_STORE static store in default project" { + run create_static_credential_store $NEW_STORE $DEFAULT_P_ID + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can create $NEW_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_credential $NEW_CREDENTIAL $csid 'username' 'password' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can not create already created $NEW_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_credential $NEW_CREDENTIAL $csid 'username' 'password' + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/credentials: can read $NEW_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can delete $NEW_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run delete_credential $cid + echo "$output" + run has_status_code "$output" "204" + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can not read deleted $NEW_CREDENTIAL credential" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run read_credential $cid + echo "$output" + [ "$status" -eq 1 ] +} diff --git a/internal/types/resource/resource.go b/internal/types/resource/resource.go index b53785030..4d30ab4a2 100644 --- a/internal/types/resource/resource.go +++ b/internal/types/resource/resource.go @@ -25,6 +25,7 @@ const ( ManagedGroup CredentialStore CredentialLibrary + Credential // NOTE: When adding a new type, be sure to update: // // * The Grant.validateType function and test @@ -58,6 +59,7 @@ func (r Type) String() string { "managed-group", "credential-store", "credential-library", + "credential", }[r] } @@ -90,4 +92,5 @@ var Map = map[string]Type{ ManagedGroup.String(): ManagedGroup, CredentialStore.String(): CredentialStore, CredentialLibrary.String(): CredentialLibrary, + Credential.String(): Credential, } diff --git a/sdk/pbs/controller/api/resources/credentials/credential.pb.go b/sdk/pbs/controller/api/resources/credentials/credential.pb.go new file mode 100644 index 000000000..cc8c3168f --- /dev/null +++ b/sdk/pbs/controller/api/resources/credentials/credential.pb.go @@ -0,0 +1,462 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc (unknown) +// source: controller/api/resources/credentials/v1/credential.proto + +package credentials + +import ( + scopes "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + _ "google.golang.org/genproto/googleapis/api/visibility" + 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) +) + +// Credential contains all fields related to an Credential resource +type Credential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Output only. The ID of the Credential. + Id string `protobuf:"bytes,10,opt,name=id,proto3" json:"id,omitempty" class:"public"` // @gotags: `class:"public"` + // The ID of the Credential Store of which this Credential is a part. + CredentialStoreId string `protobuf:"bytes,20,opt,name=credential_store_id,proto3" json:"credential_store_id,omitempty" class:"public"` // @gotags: `class:"public"` + // Output only. Scope information for this Credential. + 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" class:"public"` // @gotags: `class:"public"` + // Optional user-set description for identification purposes. + Description *wrapperspb.StringValue `protobuf:"bytes,50,opt,name=description,proto3" json:"description,omitempty" class:"public"` // @gotags: `class:"public"` + // Output only. The time this resource was created. + CreatedTime *timestamppb.Timestamp `protobuf:"bytes,60,opt,name=created_time,proto3" json:"created_time,omitempty" class:"public"` // @gotags: `class:"public"` + // Output only. The time this resource was last updated. + UpdatedTime *timestamppb.Timestamp `protobuf:"bytes,70,opt,name=updated_time,proto3" json:"updated_time,omitempty" class:"public"` // @gotags: `class:"public"` + // 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" class:"public"` // @gotags: `class:"public"` + // The Credential type. + Type string `protobuf:"bytes,90,opt,name=type,proto3" json:"type,omitempty" class:"public"` // @gotags: `class:"public"` + // Types that are assignable to Attrs: + // *Credential_Attributes + // *Credential_UsernamePasswordAttributes + Attrs isCredential_Attrs `protobuf_oneof:"attrs"` + // 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" class:"public"` // @gotags: `class:"public"` +} + +func (x *Credential) Reset() { + *x = Credential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[0] + 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_api_resources_credentials_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 Credential.ProtoReflect.Descriptor instead. +func (*Credential) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{0} +} + +func (x *Credential) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Credential) GetCredentialStoreId() string { + if x != nil { + return x.CredentialStoreId + } + return "" +} + +func (x *Credential) GetScope() *scopes.ScopeInfo { + if x != nil { + return x.Scope + } + return nil +} + +func (x *Credential) GetName() *wrapperspb.StringValue { + if x != nil { + return x.Name + } + return nil +} + +func (x *Credential) GetDescription() *wrapperspb.StringValue { + if x != nil { + return x.Description + } + return nil +} + +func (x *Credential) GetCreatedTime() *timestamppb.Timestamp { + if x != nil { + return x.CreatedTime + } + return nil +} + +func (x *Credential) GetUpdatedTime() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedTime + } + return nil +} + +func (x *Credential) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Credential) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (m *Credential) GetAttrs() isCredential_Attrs { + if m != nil { + return m.Attrs + } + return nil +} + +func (x *Credential) GetAttributes() *structpb.Struct { + if x, ok := x.GetAttrs().(*Credential_Attributes); ok { + return x.Attributes + } + return nil +} + +func (x *Credential) GetUsernamePasswordAttributes() *UsernamePasswordAttributes { + if x, ok := x.GetAttrs().(*Credential_UsernamePasswordAttributes); ok { + return x.UsernamePasswordAttributes + } + return nil +} + +func (x *Credential) GetAuthorizedActions() []string { + if x != nil { + return x.AuthorizedActions + } + return nil +} + +type isCredential_Attrs interface { + isCredential_Attrs() +} + +type Credential_Attributes struct { + // The attributes that are applicable for the specific Credential type. + Attributes *structpb.Struct `protobuf:"bytes,100,opt,name=attributes,proto3,oneof"` +} + +type Credential_UsernamePasswordAttributes struct { + UsernamePasswordAttributes *UsernamePasswordAttributes `protobuf:"bytes,101,opt,name=username_password_attributes,json=usernamePasswordAttributes,proto3,oneof"` +} + +func (*Credential_Attributes) isCredential_Attrs() {} + +func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {} + +// The attributes of a UsernamePassword Credential. +type UsernamePasswordAttributes struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // The username associated with the credential. + Username *wrapperspb.StringValue `protobuf:"bytes,10,opt,name=username,proto3" json:"username,omitempty" class:"public"` // @gotags: `class:"public"` + // Input only. The password associated with the credential. + Password *wrapperspb.StringValue `protobuf:"bytes,20,opt,name=password,proto3" json:"password,omitempty" class:"secret"` // @gotags: `class:"secret"` + // Output only. The hmac value of the password. + PasswordHmac string `protobuf:"bytes,30,opt,name=password_hmac,proto3" json:"password_hmac,omitempty" class:"public"` // @gotags: `class:"public"` +} + +func (x *UsernamePasswordAttributes) Reset() { + *x = UsernamePasswordAttributes{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_api_resources_credentials_v1_credential_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UsernamePasswordAttributes) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsernamePasswordAttributes) ProtoMessage() {} + +func (x *UsernamePasswordAttributes) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_credentials_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 UsernamePasswordAttributes.ProtoReflect.Descriptor instead. +func (*UsernamePasswordAttributes) Descriptor() ([]byte, []int) { + return file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP(), []int{1} +} + +func (x *UsernamePasswordAttributes) GetUsername() *wrapperspb.StringValue { + if x != nil { + return x.Username + } + return nil +} + +func (x *UsernamePasswordAttributes) GetPassword() *wrapperspb.StringValue { + if x != nil { + return x.Password + } + return nil +} + +func (x *UsernamePasswordAttributes) GetPasswordHmac() string { + if x != nil { + return x.PasswordHmac + } + return "" +} + +var File_controller_api_resources_credentials_v1_credential_proto protoreflect.FileDescriptor + +var file_controller_api_resources_credentials_v1_credential_proto_rawDesc = []byte{ + 0x0a, 0x38, 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, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 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, + 0x2e, 0x76, 0x31, 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, 0x1a, + 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x69, 0x73, 0x69, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 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, 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, 0x22, 0xa8, 0x06, 0x0a, 0x0a, + 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 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, 0x4a, 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, 0x0f, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, + 0x29, 0x07, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x61, 0x74, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0xb2, 0x01, 0x0a, 0x1c, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x61, 0x74, + 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x18, 0x65, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x43, + 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, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x65, 0x73, 0x42, 0x29, 0xa0, 0xda, 0x29, 0x01, 0x9a, 0xe3, 0x29, 0x11, 0x75, 0x73, 0x65, + 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0xfa, 0xd2, + 0xe4, 0x93, 0x02, 0x0a, 0x12, 0x08, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x48, 0x00, + 0x52, 0x1a, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, + 0x72, 0x64, 0x41, 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, 0x42, 0x07, 0x0a, + 0x05, 0x61, 0x74, 0x74, 0x72, 0x73, 0x22, 0xb6, 0x02, 0x0a, 0x1a, 0x55, 0x73, 0x65, 0x72, 0x6e, + 0x61, 0x6d, 0x65, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x41, 0x74, 0x74, 0x72, 0x69, + 0x62, 0x75, 0x74, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, + 0x65, 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, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, 0x29, 0x1f, 0x0a, + 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x75, 0x73, 0x65, 0x72, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x08, 0x55, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x08, + 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x61, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, + 0x77, 0x6f, 0x72, 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, 0x27, 0xa0, 0xda, 0x29, 0x01, 0xc2, 0xdd, + 0x29, 0x1f, 0x0a, 0x13, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x08, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x52, 0x0a, 0x0d, 0x70, + 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x18, 0x1e, 0x20, 0x01, + 0x28, 0x09, 0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x18, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x2e, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, + 0x61, 0x63, 0x12, 0x0c, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x48, 0x6d, 0x61, 0x63, + 0x52, 0x0d, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x5f, 0x68, 0x6d, 0x61, 0x63, 0x42, + 0x58, 0x5a, 0x56, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, + 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, + 0x2f, 0x73, 0x64, 0x6b, 0x2f, 0x70, 0x62, 0x73, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x6c, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x73, 0x2f, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x3b, 0x63, 0x72, + 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_controller_api_resources_credentials_v1_credential_proto_rawDescOnce sync.Once + file_controller_api_resources_credentials_v1_credential_proto_rawDescData = file_controller_api_resources_credentials_v1_credential_proto_rawDesc +) + +func file_controller_api_resources_credentials_v1_credential_proto_rawDescGZIP() []byte { + file_controller_api_resources_credentials_v1_credential_proto_rawDescOnce.Do(func() { + file_controller_api_resources_credentials_v1_credential_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_api_resources_credentials_v1_credential_proto_rawDescData) + }) + return file_controller_api_resources_credentials_v1_credential_proto_rawDescData +} + +var file_controller_api_resources_credentials_v1_credential_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_controller_api_resources_credentials_v1_credential_proto_goTypes = []interface{}{ + (*Credential)(nil), // 0: controller.api.resources.credentials.v1.Credential + (*UsernamePasswordAttributes)(nil), // 1: controller.api.resources.credentials.v1.UsernamePasswordAttributes + (*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_credentials_v1_credential_proto_depIdxs = []int32{ + 2, // 0: controller.api.resources.credentials.v1.Credential.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 3, // 1: controller.api.resources.credentials.v1.Credential.name:type_name -> google.protobuf.StringValue + 3, // 2: controller.api.resources.credentials.v1.Credential.description:type_name -> google.protobuf.StringValue + 4, // 3: controller.api.resources.credentials.v1.Credential.created_time:type_name -> google.protobuf.Timestamp + 4, // 4: controller.api.resources.credentials.v1.Credential.updated_time:type_name -> google.protobuf.Timestamp + 5, // 5: controller.api.resources.credentials.v1.Credential.attributes:type_name -> google.protobuf.Struct + 1, // 6: controller.api.resources.credentials.v1.Credential.username_password_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordAttributes + 3, // 7: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue + 3, // 8: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password: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_credentials_v1_credential_proto_init() } +func file_controller_api_resources_credentials_v1_credential_proto_init() { + if File_controller_api_resources_credentials_v1_credential_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_api_resources_credentials_v1_credential_proto_msgTypes[0].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_api_resources_credentials_v1_credential_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UsernamePasswordAttributes); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_controller_api_resources_credentials_v1_credential_proto_msgTypes[0].OneofWrappers = []interface{}{ + (*Credential_Attributes)(nil), + (*Credential_UsernamePasswordAttributes)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_api_resources_credentials_v1_credential_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_api_resources_credentials_v1_credential_proto_goTypes, + DependencyIndexes: file_controller_api_resources_credentials_v1_credential_proto_depIdxs, + MessageInfos: file_controller_api_resources_credentials_v1_credential_proto_msgTypes, + }.Build() + File_controller_api_resources_credentials_v1_credential_proto = out.File + file_controller_api_resources_credentials_v1_credential_proto_rawDesc = nil + file_controller_api_resources_credentials_v1_credential_proto_goTypes = nil + file_controller_api_resources_credentials_v1_credential_proto_depIdxs = nil +}