diff --git a/api/credentials/option.gen.go b/api/credentials/option.gen.go index 9b3d795902..be36627e57 100644 --- a/api/credentials/option.gen.go +++ b/api/credentials/option.gen.go @@ -179,6 +179,18 @@ func WithJsonCredentialObject(inObject map[string]interface{}) Option { } } +func WithPasswordCredentialPassword(inPassword string) Option { + return func(o *options) { + raw, ok := o.postMap["attributes"] + if !ok { + raw = any(map[string]any{}) + } + val := raw.(map[string]any) + val["password"] = inPassword + o.postMap["attributes"] = val + } +} + func WithUsernamePasswordCredentialPassword(inPassword string) Option { return func(o *options) { raw, ok := o.postMap["attributes"] diff --git a/api/credentials/password_attributes.gen.go b/api/credentials/password_attributes.gen.go new file mode 100644 index 0000000000..8bce99467a --- /dev/null +++ b/api/credentials/password_attributes.gen.go @@ -0,0 +1,41 @@ +// Code generated by "make api"; DO NOT EDIT. +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package credentials + +import ( + "fmt" + + "github.com/mitchellh/mapstructure" +) + +type PasswordAttributes struct { + Password string `json:"password,omitempty"` + PasswordHmac string `json:"password_hmac,omitempty"` +} + +func AttributesMapToPasswordAttributes(in map[string]any) (*PasswordAttributes, error) { + if in == nil { + return nil, fmt.Errorf("nil input map") + } + var out PasswordAttributes + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ + Result: &out, + TagName: "json", + }) + if err != nil { + return nil, fmt.Errorf("error creating mapstructure decoder: %w", err) + } + if err := dec.Decode(in); err != nil { + return nil, fmt.Errorf("error decoding: %w", err) + } + return &out, nil +} + +func (pt *Credential) GetPasswordAttributes() (*PasswordAttributes, error) { + if pt.Type != "password" { + return nil, fmt.Errorf("asked to fetch %s-type attributes but credential is of type %s", "password", pt.Type) + } + return AttributesMapToPasswordAttributes(pt.Attributes) +} diff --git a/internal/api/genapi/input.go b/internal/api/genapi/input.go index f204c9af12..08d374f2a2 100644 --- a/internal/api/genapi/input.go +++ b/internal/api/genapi/input.go @@ -736,6 +736,22 @@ var inputStructs = []*structInfo{ mapstructureConversionTemplate, }, }, + { + inProto: &credentials.PasswordAttributes{}, + outFile: "credentials/password_attributes.gen.go", + subtypeName: "PasswordCredential", + subtype: "password", + fieldOverrides: []fieldInfo{ + { + Name: "Password", + SkipDefault: true, + }, + }, + parentTypeName: "Credential", + templates: []*template.Template{ + mapstructureConversionTemplate, + }, + }, { inProto: &credentials.SshPrivateKeyAttributes{}, outFile: "credentials/ssh_private_key_attributes.gen.go", diff --git a/internal/daemon/controller/handlers/credentials/credential_service.go b/internal/daemon/controller/handlers/credentials/credential_service.go index d5469a108b..cffe7e0d41 100644 --- a/internal/daemon/controller/handlers/credentials/credential_service.go +++ b/internal/daemon/controller/handlers/credentials/credential_service.go @@ -49,6 +49,7 @@ var ( upMaskManager handlers.MaskManager spkMaskManager handlers.MaskManager jsonMaskManager handlers.MaskManager + pMaskmanager handlers.MaskManager // IdActions contains the set of actions that can be performed on // individual resources @@ -97,6 +98,13 @@ func init() { ); err != nil { panic(err) } + if pMaskmanager, err = handlers.NewMaskManager( + context.Background(), + handlers.MaskDestination{&store.PasswordCredential{}}, + handlers.MaskSource{&pb.Credential{}, &pb.PasswordAttributes{}}, + ); err != nil { + panic(err) + } // TODO: refactor to remove IdActionsMap and CollectionActions package variables action.RegisterResource(resource.Credential, IdActions, CollectionActions) @@ -454,6 +462,23 @@ func (s Service) createInRepo(ctx context.Context, scopeId string, item *pb.Cred return nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to create credential but no error returned from repository.") } return out, nil + case credential.PasswordSubtype.String(): + cred, err := toPasswordStorageCredential(ctx, 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.CreatePasswordCredential(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 case credential.SshPrivateKeySubtype.String(): cred, err := toSshPrivateKeyStorageCredential(ctx, item.GetCredentialStoreId(), item) if err != nil { @@ -551,6 +576,29 @@ func (s Service) updateInRepo( return nil, handlers.NotFoundErrorf("Credential %q doesn't exist or incorrect version provided.", id) } return out, nil + case credential.PasswordSubtype: + dbMasks = append(dbMasks, pMaskmanager.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."}) + } + + cred, err := toPasswordStorageCredential(ctx, storeId, in) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to convert to password storage credential")) + } + cred.PublicId = id + repo, err := s.repoFn() + if err != nil { + return nil, errors.Wrap(ctx, err, op) + } + out, rowsUpdated, err := repo.UpdatePasswordCredential(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 case credential.SshPrivateKeySubtype: dbMasks = append(dbMasks, spkMaskManager.Translate(masks)...) @@ -731,6 +779,8 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro out.Type = credential.UsernamePasswordSubtype.String() case *static.UsernamePasswordDomainCredential: out.Type = credential.UsernamePasswordDomainSubtype.String() + case *static.PasswordCredential: + out.Type = credential.PasswordSubtype.String() case *static.SshPrivateKeyCredential: out.Type = credential.SshPrivateKeySubtype.String() case *static.JsonCredential: @@ -779,6 +829,14 @@ func toProto(in credential.Static, opt ...handlers.Option) (*pb.Credential, erro }, } } + case *static.PasswordCredential: + if outputFields.Has(globals.AttributesField) { + out.Attrs = &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + PasswordHmac: base64.RawURLEncoding.EncodeToString(cred.GetPasswordHmac()), + }, + } + } case *static.SshPrivateKeyCredential: if outputFields.Has(globals.AttributesField) { out.Attrs = &pb.Credential_SshPrivateKeyAttributes{ @@ -848,6 +906,28 @@ func toUsernamePasswordDomainStorageCredential(ctx context.Context, storeId stri return cs, err } +func toPasswordStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.PasswordCredential, err error) { + const op = "credentials.toPasswordStorageCredential" + 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.GetPasswordAttributes() + cs, err := static.NewPasswordCredential( + storeId, + credential.Password(attrs.GetPassword().GetValue()), + opts...) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to build credential")) + } + + return cs, err +} + func toSshPrivateKeyStorageCredential(ctx context.Context, storeId string, in *pb.Credential) (out *static.SshPrivateKeyCredential, err error) { const op = "credentials.toSshPrivateKeyStorageCredential" var opts []static.Option @@ -919,6 +999,7 @@ func validateGetRequest(req *pbs.GetCredentialRequest) error { globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, + globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix, ) @@ -949,6 +1030,10 @@ func validateCreateRequest(req *pbs.CreateCredentialRequest) error { if req.Item.GetUsernamePasswordDomainAttributes().GetDomain().GetValue() == "" { badFields[domainField] = "Field required for creating a username-password-domain credential." } + case credential.PasswordSubtype.String(): + if req.Item.GetPasswordAttributes().GetPassword().GetValue() == "" { + badFields[passwordField] = "Field required for creating a password credential." + } case credential.SshPrivateKeySubtype.String(): if req.Item.GetSshPrivateKeyAttributes().GetUsername().GetValue() == "" { badFields[usernameField] = "Field required for creating an SSH private key credential." @@ -1018,7 +1103,11 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error { if handlers.MaskContains(req.GetUpdateMask().GetPaths(), domainField) && attrs.GetDomain().GetValue() == "" { badFields[domainField] = "This is a required field and cannot be set to empty." } - + case credential.PasswordSubtype: + attrs := req.GetItem().GetPasswordAttributes() + if handlers.MaskContains(req.GetUpdateMask().GetPaths(), passwordField) && attrs.GetPassword().GetValue() == "" { + badFields[passwordField] = "This is a required field and cannot be set to empty." + } case credential.SshPrivateKeySubtype: attrs := req.GetItem().GetSshPrivateKeyAttributes() if handlers.MaskContains(req.GetUpdateMask().GetPaths(), usernameField) && attrs.GetUsername().GetValue() == "" { @@ -1070,6 +1159,7 @@ func validateUpdateRequest(req *pbs.UpdateCredentialRequest) error { globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, + globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix, ) @@ -1082,6 +1172,7 @@ func validateDeleteRequest(req *pbs.DeleteCredentialRequest) error { globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, + globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix, ) diff --git a/internal/daemon/controller/handlers/credentials/credential_service_test.go b/internal/daemon/controller/handlers/credentials/credential_service_test.go index be53e5eac0..92bea251b7 100644 --- a/internal/daemon/controller/handlers/credentials/credential_service_test.go +++ b/internal/daemon/controller/handlers/credentials/credential_service_test.go @@ -68,7 +68,7 @@ func staticJsonCredentialToProto(cred *static.JsonCredential, prj *iam.Scope, hm } } -func staticPasswordCredentialToProto(cred *static.UsernamePasswordCredential, prj *iam.Scope, hmac string) *pb.Credential { +func staticUsernamePasswordCredentialToProto(cred *static.UsernamePasswordCredential, prj *iam.Scope, hmac string) *pb.Credential { return &pb.Credential{ Id: cred.GetPublicId(), CredentialStoreId: cred.GetStoreId(), @@ -87,7 +87,25 @@ func staticPasswordCredentialToProto(cred *static.UsernamePasswordCredential, pr } } -func staticPasswordDomainCredentialToProto(cred *static.UsernamePasswordDomainCredential, prj *iam.Scope, hmac string) *pb.Credential { +func staticPasswordCredentialToProto(cred *static.PasswordCredential, prj *iam.Scope, hmac string) *pb.Credential { + return &pb.Credential{ + Id: cred.GetPublicId(), + CredentialStoreId: cred.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + CreatedTime: cred.GetCreateTime().GetTimestamp(), + UpdatedTime: cred.GetUpdateTime().GetTimestamp(), + Version: cred.GetVersion(), + Type: credential.PasswordSubtype.String(), + AuthorizedActions: testAuthorizedActions, + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(hmac)), + }, + }, + } +} + +func staticUsernamePasswordDomainCredentialToProto(cred *static.UsernamePasswordDomainCredential, prj *iam.Scope, hmac string) *pb.Credential { return &pb.Credential{ Id: cred.GetPublicId(), CredentialStoreId: cred.GetStoreId(), @@ -155,13 +173,18 @@ func TestList(t *testing.T) { c := static.TestUsernamePasswordCredential(t, conn, wrapper, user, pass, store.GetPublicId(), prj.GetPublicId()) hm, err := crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) require.NoError(t, err) - wantCreds = append(wantCreds, staticPasswordCredentialToProto(c, prj, hm)) + wantCreds = append(wantCreds, staticUsernamePasswordCredentialToProto(c, prj, hm)) domain := fmt.Sprintf("domain-%d", i) upd := static.TestUsernamePasswordDomainCredential(t, conn, wrapper, user, pass, domain, store.GetPublicId(), prj.GetPublicId()) hm, err = crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) require.NoError(t, err) - wantCreds = append(wantCreds, staticPasswordDomainCredentialToProto(upd, prj, hm)) + wantCreds = append(wantCreds, staticUsernamePasswordDomainCredentialToProto(upd, prj, hm)) + + p := static.TestPasswordCredential(t, conn, wrapper, pass, store.GetPublicId(), prj.GetPublicId()) + hm, err = crypto.HmacSha256(ctx, []byte(pass), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + wantCreds = append(wantCreds, staticPasswordCredentialToProto(p, prj, hm)) spk := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId()) hm, err = crypto.HmacSha256(ctx, []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil) @@ -193,7 +216,7 @@ func TestList(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 40, + EstItemCount: 50, }, anonRes: &pbs.ListCredentialsResponse{ Items: wantCreds, @@ -247,9 +270,9 @@ func TestList(t *testing.T) { }, { name: "Filter on Attribute", - req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[4].GetUsernamePasswordAttributes().GetUsername().Value)}, + req: &pbs.ListCredentialsRequest{CredentialStoreId: store.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/username"==%q`, wantCreds[0].GetUsernamePasswordAttributes().GetUsername().Value)}, res: &pbs.ListCredentialsResponse{ - Items: wantCreds[4:7], + Items: []*pb.Credential{wantCreds[0], wantCreds[1], wantCreds[3]}, ResponseType: "complete", ListToken: "", SortBy: "created_time", @@ -363,6 +386,10 @@ func TestGet(t *testing.T) { updHm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) require.NoError(t, err) + pCred := static.TestPasswordCredential(t, conn, wrapper, "pass", store.GetPublicId(), prj.GetPublicId()) + pHm, err := crypto.HmacSha256(context.Background(), []byte("pass"), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(t, err) + spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId()) spkHm, err := crypto.HmacSha256(context.Background(), []byte(static.TestSshPrivateKeyPem), databaseWrapper, []byte(store.GetPublicId()), nil) require.NoError(t, err) @@ -455,7 +482,27 @@ func TestGet(t *testing.T) { }, }, }, - + { + name: "success-password-only-credential", + id: pCred.GetPublicId(), + res: &pbs.GetCredentialResponse{ + Item: &pb.Credential{ + Id: pCred.GetPublicId(), + CredentialStoreId: pCred.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: credential.PasswordSubtype.String(), + AuthorizedActions: testAuthorizedActions, + CreatedTime: pCred.CreateTime.GetTimestamp(), + UpdatedTime: pCred.UpdateTime.GetTimestamp(), + Version: 1, + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + PasswordHmac: base64.RawURLEncoding.EncodeToString([]byte(pHm)), + }, + }, + }, + }, + }, { name: "success-spk", id: spkCred.GetPublicId(), @@ -589,6 +636,7 @@ func TestDelete(t *testing.T) { upCred := static.TestUsernamePasswordCredential(t, conn, wrapper, "user", "pass", store.GetPublicId(), prj.GetPublicId()) updCred := static.TestUsernamePasswordDomainCredential(t, conn, wrapper, "user", "pass", "domain", store.GetPublicId(), prj.GetPublicId()) + pCred := static.TestPasswordCredential(t, conn, wrapper, "pass", store.GetPublicId(), prj.GetPublicId()) spkCred := static.TestSshPrivateKeyCredential(t, conn, wrapper, "user", static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId()) obj, _ := static.TestJsonObject(t) @@ -609,6 +657,10 @@ func TestDelete(t *testing.T) { name: "success-upd", id: updCred.GetPublicId(), }, + { + name: "success-p", + id: pCred.GetPublicId(), + }, { name: "success-spk", id: spkCred.GetPublicId(), @@ -906,6 +958,92 @@ func TestCreate(t *testing.T) { res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, + { + name: "Can't specify Id P", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Id: globals.PasswordCredentialPrefix + "_notallowed", + Type: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + Password: wrapperspb.String("password"), + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, + { + name: "Invalid Credential Store Id P", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: "p_invalidid", + Type: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + 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: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + Password: wrapperspb.String("password"), + }, + }, + }}, + 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: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + 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_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + 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: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + // Empty password + }, + }, + }}, + res: nil, + err: handlers.ApiErrorWithCode(codes.InvalidArgument), + }, { name: "Must provide private key", req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ @@ -959,6 +1097,32 @@ func TestCreate(t *testing.T) { }, }, }, + { + name: "valid-p", + req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ + CredentialStoreId: store.GetPublicId(), + Type: credential.PasswordSubtype.String(), + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + Password: wrapperspb.String("password"), + }, + }, + }}, + idPrefix: globals.PasswordCredentialPrefix + "_", + res: &pbs.CreateCredentialResponse{ + Uri: fmt.Sprintf("credentials/%s_", globals.PasswordCredentialPrefix), + 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: credential.PasswordSubtype.String(), + AuthorizedActions: testAuthorizedActions, + }, + }, + }, { name: "valid-spk", req: &pbs.CreateCredentialRequest{Item: &pb.Credential{ @@ -1086,6 +1250,15 @@ func TestCreate(t *testing.T) { assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetUsernamePasswordAttributes().GetPasswordHmac()) assert.Empty(got.GetItem().GetUsernamePasswordAttributes().GetPassword()) + case credential.PasswordSubtype.String(): + password := tc.req.GetItem().GetPasswordAttributes().GetPassword().GetValue() + hm, err := crypto.HmacSha256(ctx, []byte(password), databaseWrapper, []byte(store.GetPublicId()), nil, crypto.WithEd25519()) + require.NoError(err) + + // Validate attributes equal + assert.Equal(base64.RawURLEncoding.EncodeToString([]byte(hm)), got.GetItem().GetPasswordAttributes().GetPasswordHmac()) + assert.Empty(got.GetItem().GetPasswordAttributes().GetPassword()) + case credential.SshPrivateKeySubtype.String(): pk := tc.req.GetItem().GetSshPrivateKeyAttributes().GetPrivateKey().GetValue() hm, err := crypto.HmacSha256(ctx, []byte(pk), databaseWrapper, []byte(store.GetPublicId()), nil) @@ -1193,6 +1366,16 @@ func TestUpdate(t *testing.T) { return cred, clean } + freshCredP := func(pass string) (*static.PasswordCredential, func()) { + t.Helper() + cred := static.TestPasswordCredential(t, conn, wrapper, pass, store.GetPublicId(), prj.GetPublicId()) + clean := func() { + _, err := s.DeleteCredential(ctx, &pbs.DeleteCredentialRequest{Id: cred.GetPublicId()}) + require.NoError(t, err) + } + return cred, clean + } + freshCredSpk := func(user string) (*static.SshPrivateKeyCredential, func()) { t.Helper() cred := static.TestSshPrivateKeyCredential(t, conn, wrapper, user, static.TestSshPrivateKeyPem, store.GetPublicId(), prj.GetPublicId()) @@ -1405,6 +1588,26 @@ func TestUpdate(t *testing.T) { return out }, }, + { + name: "update-password-attributes-passwordOnly", + req: &pbs.UpdateCredentialRequest{ + UpdateMask: fieldmask("attributes.password"), + Item: &pb.Credential{ + Attrs: &pb.Credential_PasswordAttributes{ + PasswordAttributes: &pb.PasswordAttributes{ + 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.GetPasswordAttributes().PasswordHmac = base64.RawURLEncoding.EncodeToString([]byte(hm)) + return out + }, + }, { name: "update-spk", req: &pbs.UpdateCredentialRequest{ @@ -1663,6 +1866,8 @@ func TestUpdate(t *testing.T) { cred, cleanup = freshCredJson() } else if strings.Contains(tc.name, "domainupd") { cred, cleanup = freshCredUpd("user", "pass", "domain") + } else if strings.Contains(tc.name, "passwordOnly") { + cred, cleanup = freshCredP("pass") } else { cred, cleanup = freshCredUp("user", "pass") } @@ -1716,6 +1921,14 @@ func TestUpdate(t *testing.T) { credUp, cleanUp := freshCredUp("user", "pass") defer cleanUp() + // cant update read only fields + credUpd, cleanUpd := freshCredUpd("user", "pass", "domain") + defer cleanUpd() + + // cant update read only fields + credP, cleanP := freshCredP("pass") + defer cleanP() + newStore := static.TestCredentialStore(t, conn, wrapper, prj.GetPublicId()) roCases := []struct { @@ -1781,6 +1994,42 @@ func TestUpdate(t *testing.T) { } matcher(t, gErr) assert.Nil(t, got) + + req = &pbs.UpdateCredentialRequest{ + Id: credUpd.GetPublicId(), + Item: tc.item, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}}, + } + req.Item.Version = credUpd.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) + + req = &pbs.UpdateCredentialRequest{ + Id: credP.GetPublicId(), + Item: tc.item, + UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{tc.path}}, + } + req.Item.Version = credP.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) }) } } @@ -1841,12 +2090,12 @@ func TestListPagination(t *testing.T) { for _, l := range static.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "username", "password", "domain", credStore.GetPublicId(), prj.PublicId, 5) { hm, err := crypto.HmacSha256(ctx, []byte("password"), databaseWrapper, []byte(credStore.GetPublicId()), nil, crypto.WithEd25519()) require.NoError(err) - allCredentials = append(allCredentials, staticPasswordDomainCredentialToProto(l, prj, hm)) + allCredentials = append(allCredentials, staticUsernamePasswordDomainCredentialToProto(l, prj, hm)) } for _, l := range static.TestUsernamePasswordCredentials(t, conn, wrapper, "username", "password", credStore.GetPublicId(), prj.PublicId, 5) { hm, err := crypto.HmacSha256(ctx, []byte("password"), databaseWrapper, []byte(credStore.GetPublicId()), nil, crypto.WithEd25519()) require.NoError(err) - allCredentials = append(allCredentials, staticPasswordCredentialToProto(l, prj, hm)) + allCredentials = append(allCredentials, staticUsernamePasswordCredentialToProto(l, prj, hm)) } // Reverse slices since response is ordered by created_time descending (newest first) diff --git a/internal/daemon/controller/handlers/targets/credentials.go b/internal/daemon/controller/handlers/targets/credentials.go index ec3a2c2f75..392bd2e19c 100644 --- a/internal/daemon/controller/handlers/targets/credentials.go +++ b/internal/daemon/controller/handlers/targets/credentials.go @@ -275,6 +275,21 @@ func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for username password domain credential")) } + case *credstatic.PasswordCredential: + var err error + credType = string(globals.PasswordCredentialType) + credData, err = handlers.ProtoToStruct( + ctx, + &pb.PasswordCredential{ + Password: string(c.GetPassword()), + }, + ) + secret = map[string]any{ + "password": string(c.GetPassword()), + } + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for password credential")) + } case *credstatic.SshPrivateKeyCredential: var err error credType = string(globals.SshPrivateKeyCredentialType) @@ -296,7 +311,6 @@ func staticToSessionCredential(ctx context.Context, cred credential.Static) (*pb if err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for ssh private key credential")) } - case *credstatic.JsonCredential: var err error credType = string(globals.JsonCredentialType) diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go index 22d619c0a9..1768291046 100644 --- a/internal/daemon/controller/handlers/targets/target_service.go +++ b/internal/daemon/controller/handlers/targets/target_service.go @@ -2075,7 +2075,6 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, - globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break @@ -2117,7 +2116,6 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, - globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break @@ -2163,7 +2161,6 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc globals.UsernamePasswordCredentialPrefix, globals.UsernamePasswordCredentialPreviousPrefix, globals.UsernamePasswordDomainCredentialPrefix, - globals.PasswordCredentialPrefix, globals.SshPrivateKeyCredentialPrefix, globals.JsonCredentialPrefix) { badFields[globals.InjectedApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) diff --git a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go index a372d713fa..96ab0ad19b 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -3085,6 +3085,7 @@ func TestAddTargetCredentialSources(t *testing.T) { storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", storeStatic.GetPublicId(), proj.GetPublicId(), 2) + pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) addCases := []struct { name string @@ -3110,6 +3111,12 @@ func TestAddTargetCredentialSources(t *testing.T) { addSources: []string{updCreds[1].GetPublicId()}, resultSourceIds: []string{updCreds[1].GetPublicId()}, }, + { + name: "Add static p cred on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for static_p sources"), + addSources: []string{pCreds[1].GetPublicId()}, + resultSourceIds: []string{pCreds[1].GetPublicId()}, + }, { name: "Add library on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), @@ -3128,6 +3135,12 @@ func TestAddTargetCredentialSources(t *testing.T) { addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{updCreds[0].GetPublicId(), cls[1].GetPublicId()}, }, + { + name: "Add library on static p cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-static_p sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})), + addSources: []string{cls[1].GetPublicId()}, + resultSourceIds: []string{pCreds[0].GetPublicId(), cls[1].GetPublicId()}, + }, { name: "Add static cred on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), @@ -3140,6 +3153,12 @@ func TestAddTargetCredentialSources(t *testing.T) { addSources: []string{updCreds[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), updCreds[1].GetPublicId()}, }, + { + name: "Add p static cred on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static_p-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), + addSources: []string{pCreds[1].GetPublicId()}, + resultSourceIds: []string{cls[0].GetPublicId(), pCreds[1].GetPublicId()}, + }, { name: "Add static cred on static cred populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), @@ -3152,6 +3171,12 @@ func TestAddTargetCredentialSources(t *testing.T) { addSources: []string{updCreds[1].GetPublicId()}, resultSourceIds: []string{creds[0].GetPublicId(), updCreds[1].GetPublicId()}, }, + { + name: "Add static p cred on static cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static_p-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), + addSources: []string{pCreds[1].GetPublicId()}, + resultSourceIds: []string{creds[0].GetPublicId(), pCreds[1].GetPublicId()}, + }, { name: "Add duplicated sources on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), @@ -3170,6 +3195,12 @@ func TestAddTargetCredentialSources(t *testing.T) { addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), updCreds[1].GetPublicId(), updCreds[1].GetPublicId()}, resultSourceIds: []string{updCreds[0].GetPublicId(), cls[1].GetPublicId(), updCreds[1].GetPublicId()}, }, + { + name: "Add duplicated sources on static p cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for static p sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})), + addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), pCreds[1].GetPublicId(), pCreds[1].GetPublicId()}, + resultSourceIds: []string{pCreds[0].GetPublicId(), cls[1].GetPublicId(), pCreds[1].GetPublicId()}, + }, } for _, tc := range addCases { @@ -3296,6 +3327,7 @@ func TestSetTargetCredentialSources(t *testing.T) { storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", storeStatic.GetPublicId(), proj.GetPublicId(), 2) + pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) setCases := []struct { name string @@ -3321,6 +3353,12 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources: []string{updCreds[1].GetPublicId()}, resultCredentialSourceIds: []string{updCreds[1].GetPublicId()}, }, + { + name: "Set static_p on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static_p"), + setCredentialSources: []string{pCreds[1].GetPublicId()}, + resultCredentialSourceIds: []string{pCreds[1].GetPublicId()}, + }, { name: "Set library on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), @@ -3339,6 +3377,12 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources: []string{updCreds[1].GetPublicId()}, resultCredentialSourceIds: []string{updCreds[1].GetPublicId()}, }, + { + name: "Set static_p on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static_p-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), + setCredentialSources: []string{pCreds[1].GetPublicId()}, + resultCredentialSourceIds: []string{pCreds[1].GetPublicId()}, + }, { name: "Set library on static populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), @@ -3351,6 +3395,12 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, + { + name: "Set library on static_p populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})), + setCredentialSources: []string{cls[1].GetPublicId()}, + resultCredentialSourceIds: []string{cls[1].GetPublicId()}, + }, { name: "Set static on static populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), @@ -3363,12 +3413,24 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources: []string{updCreds[1].GetPublicId()}, resultCredentialSourceIds: []string{updCreds[1].GetPublicId()}, }, + { + name: "Set static_p on static populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static_p-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), + setCredentialSources: []string{pCreds[1].GetPublicId()}, + resultCredentialSourceIds: []string{pCreds[1].GetPublicId()}, + }, { name: "Set static on static_upd populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static_upd", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, + { + name: "Set static on static_p populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})), + setCredentialSources: []string{creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[1].GetPublicId()}, + }, { name: "Set duplicate library on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))})), @@ -3387,12 +3449,19 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources: []string{updCreds[1].GetPublicId(), updCreds[1].GetPublicId()}, resultCredentialSourceIds: []string{updCreds[1].GetPublicId()}, }, + { + name: "Set duplicate static_p on populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate static_p", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)})), + setCredentialSources: []string{pCreds[1].GetPublicId(), pCreds[1].GetPublicId()}, + resultCredentialSourceIds: []string{pCreds[1].GetPublicId()}, + }, { name: "Set empty on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose, string(cls[0].CredentialType()))}), target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)}), target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose)}), + target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose)}), ), setCredentialSources: []string{}, resultCredentialSourceIds: nil, @@ -3509,6 +3578,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) { csStatic := credstatic.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", csStatic.GetPublicId(), proj.GetPublicId(), 2) updCreds := credstatic.TestUsernamePasswordDomainCredentials(t, conn, wrapper, "user", "pass", "domain", csStatic.GetPublicId(), proj.GetPublicId(), 2) + pCreds := credstatic.TestPasswordCredentials(t, conn, wrapper, "pass", csStatic.GetPublicId(), proj.GetPublicId(), 2) removeCases := []struct { name string @@ -3535,6 +3605,12 @@ func TestRemoveTargetCredentialSources(t *testing.T) { removeCredentialSources: []string{updCreds[1].GetPublicId()}, wantErr: true, }, + { + name: "Remove static_p from empty", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static_p"), + removeCredentialSources: []string{pCreds[1].GetPublicId()}, + wantErr: true, + }, { name: "Remove 1 of 2 libraries", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial lib", @@ -3565,6 +3641,16 @@ func TestRemoveTargetCredentialSources(t *testing.T) { removeCredentialSources: []string{updCreds[1].GetPublicId()}, resultCredentialSourceIds: []string{updCreds[0].GetPublicId()}, }, + { + name: "Remove 1 of 2 static_p credentials", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial static_p", + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose), + })), + removeCredentialSources: []string{pCreds[1].GetPublicId()}, + resultCredentialSourceIds: []string{pCreds[0].GetPublicId()}, + }, { name: "Remove 1 duplicate set of 2 libraries", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate lib", @@ -3601,6 +3687,19 @@ func TestRemoveTargetCredentialSources(t *testing.T) { }, resultCredentialSourceIds: []string{updCreds[0].GetPublicId()}, }, + { + name: "Remove 1 duplicate set of 2 static_p credentials", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate static_p", + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose), + })), + removeCredentialSources: []string{ + pCreds[1].GetPublicId(), pCreds[1].GetPublicId(), + }, + resultCredentialSourceIds: []string{pCreds[0].GetPublicId()}, + }, + { name: "Remove mixed sources from target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove mixed", @@ -3613,12 +3712,14 @@ func TestRemoveTargetCredentialSources(t *testing.T) { target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", updCreds[1].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ - cls[1].GetPublicId(), creds[0].GetPublicId(), updCreds[0].GetPublicId(), + cls[1].GetPublicId(), creds[0].GetPublicId(), updCreds[0].GetPublicId(), pCreds[0].GetPublicId(), }, resultCredentialSourceIds: []string{ - cls[0].GetPublicId(), creds[1].GetPublicId(), updCreds[1].GetPublicId(), + cls[0].GetPublicId(), creds[1].GetPublicId(), updCreds[1].GetPublicId(), pCreds[1].GetPublicId(), }, }, { @@ -3633,11 +3734,14 @@ func TestRemoveTargetCredentialSources(t *testing.T) { target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", updCreds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", updCreds[1].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[0].GetPublicId(), credential.BrokeredPurpose), + target.TestNewStaticCredential("", pCreds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), creds[0].GetPublicId(), creds[1].GetPublicId(), updCreds[0].GetPublicId(), updCreds[1].GetPublicId(), + pCreds[0].GetPublicId(), pCreds[1].GetPublicId(), }, resultCredentialSourceIds: []string{}, }, @@ -4283,6 +4387,20 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { require.NoError(t, err) require.NotNil(t, updCredResp) + pCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ + CredentialStoreId: staticStore.GetPublicId(), + Type: credential.PasswordSubtype.String(), + Name: wrapperspb.String("P Cred Name"), + Description: wrapperspb.String("P Cred Description"), + Attrs: &credpb.Credential_PasswordAttributes{ + PasswordAttributes: &credpb.PasswordAttributes{ + Password: wrapperspb.String("static-password"), + }, + }, + }}) + require.NoError(t, err) + require.NotNil(t, pCredResp) + sshPkCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ CredentialStoreId: staticStore.GetPublicId(), Type: credential.SshPrivateKeySubtype.String(), @@ -4588,6 +4706,32 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { }, wantedConnectionLimit: 1000, }, + { + name: "static-Password", + hostSourceId: shs.GetPublicId(), + credSourceId: pCredResp.GetItem().GetId(), + wantedHostId: h.GetPublicId(), + wantedEndpoint: h.GetAddress(), + wantedCred: &pb.SessionCredential{ + CredentialSource: &pb.CredentialSource{ + Id: pCredResp.GetItem().GetId(), + Name: pCredResp.GetItem().GetName().GetValue(), + Description: pCredResp.GetItem().GetDescription().GetValue(), + CredentialStoreId: staticStore.GetPublicId(), + Type: credstatic.Subtype.String(), + CredentialType: string(globals.PasswordCredentialType), + }, + Credential: func() *structpb.Struct { + data := map[string]any{ + "password": "static-password", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, + wantedConnectionLimit: 1000, + }, { name: "static-ssh-private-key", hostSourceId: shs.GetPublicId(), diff --git a/internal/proto/controller/api/resources/credentials/v1/credential.proto b/internal/proto/controller/api/resources/credentials/v1/credential.proto index 0243c7e28a..1111386075 100644 --- a/internal/proto/controller/api/resources/credentials/v1/credential.proto +++ b/internal/proto/controller/api/resources/credentials/v1/credential.proto @@ -82,6 +82,11 @@ message Credential { (custom_options.v1.generate_sdk_option) = true, (custom_options.v1.subtype) = "username_password_domain" ]; + PasswordAttributes password_attributes = 105 [ + (google.api.field_visibility).restriction = "INTERNAL", + (custom_options.v1.generate_sdk_option) = true, + (custom_options.v1.subtype) = "password" + ]; } // Output only. The available actions on this resource for this user. diff --git a/sdk/pbs/controller/api/resources/credentials/credential.pb.go b/sdk/pbs/controller/api/resources/credentials/credential.pb.go index aa382ab11e..84f57abfba 100644 --- a/sdk/pbs/controller/api/resources/credentials/credential.pb.go +++ b/sdk/pbs/controller/api/resources/credentials/credential.pb.go @@ -59,6 +59,7 @@ type Credential struct { // *Credential_SshPrivateKeyAttributes // *Credential_JsonAttributes // *Credential_UsernamePasswordDomainAttributes + // *Credential_PasswordAttributes 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"` @@ -211,6 +212,15 @@ func (x *Credential) GetUsernamePasswordDomainAttributes() *UsernamePasswordDoma return nil } +func (x *Credential) GetPasswordAttributes() *PasswordAttributes { + if x != nil { + if x, ok := x.Attrs.(*Credential_PasswordAttributes); ok { + return x.PasswordAttributes + } + } + return nil +} + func (x *Credential) GetAuthorizedActions() []string { if x != nil { return x.AuthorizedActions @@ -243,6 +253,10 @@ type Credential_UsernamePasswordDomainAttributes struct { UsernamePasswordDomainAttributes *UsernamePasswordDomainAttributes `protobuf:"bytes,104,opt,name=username_password_domain_attributes,json=usernamePasswordDomainAttributes,proto3,oneof"` } +type Credential_PasswordAttributes struct { + PasswordAttributes *PasswordAttributes `protobuf:"bytes,105,opt,name=password_attributes,json=passwordAttributes,proto3,oneof"` +} + func (*Credential_Attributes) isCredential_Attrs() {} func (*Credential_UsernamePasswordAttributes) isCredential_Attrs() {} @@ -253,6 +267,8 @@ func (*Credential_JsonAttributes) isCredential_Attrs() {} func (*Credential_UsernamePasswordDomainAttributes) isCredential_Attrs() {} +func (*Credential_PasswordAttributes) isCredential_Attrs() {} + // The attributes of a UsernamePassword Credential. type UsernamePasswordAttributes struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -584,8 +600,7 @@ var File_controller_api_resources_credentials_v1_credential_proto protoreflect.F const file_controller_api_resources_credentials_v1_credential_proto_rawDesc = "" + "\n" + - "8controller/api/resources/credentials/v1/credential.proto\x12'controller.api.resources.credentials.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xa5\n" + - "\n" + + "8controller/api/resources/credentials/v1/credential.proto\x12'controller.api.resources.credentials.v1\x1a.controller/api/resources/scopes/v1/scope.proto\x1a*controller/custom_options/v1/options.proto\x1a\x1bgoogle/api/visibility.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1egoogle/protobuf/wrappers.proto\"\xb8\v\n" + "\n" + "Credential\x12\x0e\n" + "\x02id\x18\n" + @@ -610,7 +625,9 @@ const file_controller_api_resources_credentials_v1_credential_proto_rawDesc = "" "\x0fjson_attributes\x18g \x01(\v27.controller.api.resources.credentials.v1.JsonAttributesB\x1c\xa0\xda)\x01\x9a\xe3)\x04json\xfa\xd2\xe4\x93\x02\n" + "\x12\bINTERNALH\x00R\x0ejsonAttributes\x12\xcc\x01\n" + "#username_password_domain_attributes\x18h \x01(\v2I.controller.api.resources.credentials.v1.UsernamePasswordDomainAttributesB0\xa0\xda)\x01\x9a\xe3)\x18username_password_domain\xfa\xd2\xe4\x93\x02\n" + - "\x12\bINTERNALH\x00R usernamePasswordDomainAttributes\x12/\n" + + "\x12\bINTERNALH\x00R usernamePasswordDomainAttributes\x12\x90\x01\n" + + "\x13password_attributes\x18i \x01(\v2;.controller.api.resources.credentials.v1.PasswordAttributesB \xa0\xda)\x01\x9a\xe3)\bpassword\xfa\xd2\xe4\x93\x02\n" + + "\x12\bINTERNALH\x00R\x12passwordAttributes\x12/\n" + "\x12authorized_actions\x18\xac\x02 \x03(\tR\x12authorized_actionsB\a\n" + "\x05attrs\"\xb6\x02\n" + "\x1aUsernamePasswordAttributes\x12a\n" + @@ -694,21 +711,22 @@ var file_controller_api_resources_credentials_v1_credential_proto_depIdxs = []in 4, // 7: controller.api.resources.credentials.v1.Credential.ssh_private_key_attributes:type_name -> controller.api.resources.credentials.v1.SshPrivateKeyAttributes 5, // 8: controller.api.resources.credentials.v1.Credential.json_attributes:type_name -> controller.api.resources.credentials.v1.JsonAttributes 2, // 9: controller.api.resources.credentials.v1.Credential.username_password_domain_attributes:type_name -> controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes - 7, // 10: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue - 7, // 11: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue - 7, // 12: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.username:type_name -> google.protobuf.StringValue - 7, // 13: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.password:type_name -> google.protobuf.StringValue - 7, // 14: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.domain:type_name -> google.protobuf.StringValue - 7, // 15: controller.api.resources.credentials.v1.PasswordAttributes.password:type_name -> google.protobuf.StringValue - 7, // 16: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue - 7, // 17: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue - 7, // 18: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue - 9, // 19: controller.api.resources.credentials.v1.JsonAttributes.object:type_name -> google.protobuf.Struct - 20, // [20:20] is the sub-list for method output_type - 20, // [20:20] is the sub-list for method input_type - 20, // [20:20] is the sub-list for extension type_name - 20, // [20:20] is the sub-list for extension extendee - 0, // [0:20] is the sub-list for field type_name + 3, // 10: controller.api.resources.credentials.v1.Credential.password_attributes:type_name -> controller.api.resources.credentials.v1.PasswordAttributes + 7, // 11: controller.api.resources.credentials.v1.UsernamePasswordAttributes.username:type_name -> google.protobuf.StringValue + 7, // 12: controller.api.resources.credentials.v1.UsernamePasswordAttributes.password:type_name -> google.protobuf.StringValue + 7, // 13: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.username:type_name -> google.protobuf.StringValue + 7, // 14: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.password:type_name -> google.protobuf.StringValue + 7, // 15: controller.api.resources.credentials.v1.UsernamePasswordDomainAttributes.domain:type_name -> google.protobuf.StringValue + 7, // 16: controller.api.resources.credentials.v1.PasswordAttributes.password:type_name -> google.protobuf.StringValue + 7, // 17: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.username:type_name -> google.protobuf.StringValue + 7, // 18: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key:type_name -> google.protobuf.StringValue + 7, // 19: controller.api.resources.credentials.v1.SshPrivateKeyAttributes.private_key_passphrase:type_name -> google.protobuf.StringValue + 9, // 20: controller.api.resources.credentials.v1.JsonAttributes.object:type_name -> google.protobuf.Struct + 21, // [21:21] is the sub-list for method output_type + 21, // [21:21] is the sub-list for method input_type + 21, // [21:21] is the sub-list for extension type_name + 21, // [21:21] is the sub-list for extension extendee + 0, // [0:21] is the sub-list for field type_name } func init() { file_controller_api_resources_credentials_v1_credential_proto_init() } @@ -722,6 +740,7 @@ func file_controller_api_resources_credentials_v1_credential_proto_init() { (*Credential_SshPrivateKeyAttributes)(nil), (*Credential_JsonAttributes)(nil), (*Credential_UsernamePasswordDomainAttributes)(nil), + (*Credential_PasswordAttributes)(nil), } type x struct{} out := protoimpl.TypeBuilder{