diff --git a/internal/credential/credential.go b/internal/credential/credential.go index f28ee9b925..038a54f728 100644 --- a/internal/credential/credential.go +++ b/internal/credential/credential.go @@ -143,9 +143,9 @@ type UsernamePasswordDomain interface { Domain() string } -// PasswordCredential is a credential containing a username and a password. +// PasswordOnly is a credential containing a password. // Does not follow naming convention to avoid conflict with existing Password type. -type PasswordCredential interface { +type PasswordOnly interface { Credential Password() Password } diff --git a/internal/credential/vault/private_library.go b/internal/credential/vault/private_library.go index 7843c87b21..0011a6df04 100644 --- a/internal/credential/vault/private_library.go +++ b/internal/credential/vault/private_library.go @@ -169,7 +169,7 @@ func baseToUsrPassDomain(ctx context.Context, bc *baseCred) (*usrPassDomainCred, }, nil } -var _ credential.PasswordCredential = (*passCred)(nil) +var _ credential.PasswordOnly = (*passCred)(nil) type passCred struct { *baseCred diff --git a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go index b6a61c335a..dcfe81c804 100644 --- a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go +++ b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service.go @@ -83,6 +83,7 @@ var ( globals.SshPrivateKeyCredentialType, globals.UnspecifiedCredentialType, globals.UsernamePasswordDomainCredentialType, + globals.PasswordCredentialType, } validKeyTypes = []string{ @@ -851,6 +852,11 @@ func toProto(ctx context.Context, in credential.Library, opt ...handlers.Option) m[domainAttribute] = mapping.DomainAttribute } + case *vault.PasswordOverride: + if mapping.PasswordAttribute != "" { + m[passwordAttribute] = mapping.PasswordAttribute + } + case *vault.SshPrivateKeyOverride: if mapping.UsernameAttribute != "" { m[usernameAttribute] = mapping.UsernameAttribute @@ -1006,6 +1012,17 @@ func toStorageVaultLibrary(ctx context.Context, storeId string, in *pb.Credentia opts = append(opts, vault.WithMappingOverride(vault.NewUsernamePasswordDomainOverride(mapOpts...))) } + case globals.PasswordCredentialType: + opts = append(opts, vault.WithCredentialType(credentialType)) + overrides := in.CredentialMappingOverrides.AsMap() + var mapOpts []vault.Option + if password := overrides[passwordAttribute]; password != nil { + mapOpts = append(mapOpts, vault.WithOverridePasswordAttribute(password.(string))) + } + if len(mapOpts) > 0 { + opts = append(opts, vault.WithMappingOverride(vault.NewPasswordOverride(mapOpts...))) + } + case globals.SshPrivateKeyCredentialType: opts = append(opts, vault.WithCredentialType(credentialType)) overrides := in.CredentialMappingOverrides.AsMap() @@ -1340,6 +1357,8 @@ func validateMapping(badFields map[string]string, credentialType globals.Credent validFields[usernameAttribute] = true validFields[passwordAttribute] = true validFields[domainAttribute] = true + case globals.PasswordCredentialType: + validFields[passwordAttribute] = true default: badFields[globals.CredentialTypeField] = fmt.Sprintf("Unknown credential type %q", credentialType) return @@ -1457,6 +1476,18 @@ func getMappingUpdates(credentialType globals.CredentialType, current vault.Mapp default: ret[domainAttribute] = currentDomain } + case globals.PasswordCredentialType: + var currentPass any + if overrides, ok := current.(*vault.PasswordOverride); ok { + currentPass = overrides.PasswordAttribute + } + + switch { + case masks[passwordAttribute]: + ret[passwordAttribute] = new[passwordAttribute] + default: + ret[passwordAttribute] = currentPass + } case globals.SshPrivateKeyCredentialType: var currentUser, currentpPass, currentPk any if overrides, ok := current.(*vault.SshPrivateKeyOverride); ok { diff --git a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go index e4b59200d3..4552b9a7d8 100644 --- a/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go +++ b/internal/daemon/controller/handlers/credentiallibraries/credentiallibrary_service_test.go @@ -14,7 +14,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" @@ -87,25 +86,6 @@ func sshCredentialLibraryToProto(credLib *vault.SSHCertificateCredentialLibrary, } } -func ldapCredentialLibraryToProto(credLib *vault.LdapCredentialLibrary, project *iam.Scope) *pb.CredentialLibrary { - return &pb.CredentialLibrary{ - Id: credLib.GetPublicId(), - CredentialStoreId: credLib.GetStoreId(), - CredentialType: credLib.GetCredentialType(), - Scope: &scopepb.ScopeInfo{Id: project.GetPublicId(), Type: scope.Project.String(), ParentScopeId: project.GetParentId()}, - CreatedTime: credLib.GetCreateTime().GetTimestamp(), - UpdatedTime: credLib.GetUpdateTime().GetTimestamp(), - Version: credLib.GetVersion(), - Type: vault.LdapCredentialLibrarySubtype.String(), - AuthorizedActions: testAuthorizedActions, - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String(credLib.GetVaultPath()), - }, - }, - } -} - func TestList(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") @@ -134,9 +114,6 @@ func TestList(t *testing.T) { for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) { wantLibraries = append(wantLibraries, sshCredentialLibraryToProto(l, prj)) } - for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 10) { - wantLibraries = append(wantLibraries, ldapCredentialLibraryToProto(l, prj)) - } cases := []struct { name string @@ -154,7 +131,7 @@ func TestList(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 30, + EstItemCount: 20, }, anonRes: &pbs.ListCredentialLibrariesResponse{ Items: wantLibraries, @@ -162,7 +139,7 @@ func TestList(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 30, + EstItemCount: 20, }, }, { @@ -287,20 +264,16 @@ func TestList_Attributes(t *testing.T) { _, prj := iam.TestScopes(t, iamRepo) - ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 3) - storeGeneric, storeSSHCertificate, storeLdap := ts[0], ts[1], ts[2] + ts := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 2) + storeGeneric, storeSSHCertificate := ts[0], ts[1] var wantLibrariesGeneric []*pb.CredentialLibrary var wantLibrariesSSHCertificate []*pb.CredentialLibrary - var wantLibrariesLdap []*pb.CredentialLibrary for _, l := range vault.TestCredentialLibraries(t, conn, wrapper, storeGeneric.GetPublicId(), globals.UnspecifiedCredentialType, 5) { wantLibrariesGeneric = append(wantLibrariesGeneric, vaultCredentialLibraryToProto(l, prj)) } for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, storeSSHCertificate.GetPublicId(), 10) { wantLibrariesSSHCertificate = append(wantLibrariesSSHCertificate, sshCredentialLibraryToProto(l, prj)) } - for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, storeLdap.GetPublicId(), 10) { - wantLibrariesLdap = append(wantLibrariesLdap, ldapCredentialLibraryToProto(l, prj)) - } cases := []struct { name string @@ -345,24 +318,6 @@ func TestList_Attributes(t *testing.T) { RemovedIds: nil, }, // anonymous user does not have access to attributes }, - { - name: "Filter on Attribute Ldap Library", - req: &pbs.ListCredentialLibrariesRequest{CredentialStoreId: storeLdap.GetPublicId(), Filter: fmt.Sprintf(`"/item/attributes/path"==%q`, wantLibrariesLdap[2].GetVaultLdapCredentialLibraryAttributes().GetPath().Value)}, - res: &pbs.ListCredentialLibrariesResponse{ - Items: wantLibrariesLdap[2:3], - ResponseType: "complete", - SortBy: "created_time", - SortDir: "desc", - RemovedIds: nil, - EstItemCount: 1, - }, - anonRes: &pbs.ListCredentialLibrariesResponse{ - ResponseType: "complete", - SortBy: "created_time", - SortDir: "desc", - RemovedIds: nil, - }, // anonymous user does not have access to attributes - }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -632,47 +587,26 @@ func TestCreate(t *testing.T) { err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { - name: "No Vault LDAP path", - req: &pbs.CreateCredentialLibraryRequest{ - Item: &pb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), - Type: vault.LdapCredentialLibrarySubtype.String(), - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String(""), - }, - }, - }, - }, - res: nil, - err: handlers.ApiErrorWithCode(codes.InvalidArgument), - }, - { - name: "Invalid Vault LDAP credential type", - req: &pbs.CreateCredentialLibraryRequest{ - Item: &pb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), - Type: vault.LdapCredentialLibrarySubtype.String(), - CredentialType: string(globals.SshPrivateKeyCredentialType), - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("ldap/invalid/path"), - }, + name: "Invalid password mapping", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), }, }, - }, - res: nil, - err: handlers.ApiErrorWithCode(codes.InvalidArgument), - }, - { - name: "Invalid credential library type", - req: &pbs.CreateCredentialLibraryRequest{ - Item: &pb.CredentialLibrary{ - CredentialStoreId: store.GetPublicId(), - Type: "bla-invalid", - CredentialType: "bla-invalid", - }, - }, + CredentialType: string(globals.PasswordCredentialType), + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: "password-test", + "invalid": "invalid-test", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, @@ -1074,7 +1008,7 @@ func TestCreate(t *testing.T) { }, }, { - name: "Create a valid vault CredentialLibrary ssh_private_key type", + name: "Create a valid vault CredentialLibrary password type", req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), @@ -1083,7 +1017,7 @@ func TestCreate(t *testing.T) { Path: wrapperspb.String("something"), }, }, - CredentialType: string(globals.SshPrivateKeyCredentialType), + CredentialType: string(globals.PasswordCredentialType), }}, idPrefix: globals.VaultCredentialLibraryPrefix + "_", res: &pbs.CreateCredentialLibraryResponse{ @@ -1102,13 +1036,13 @@ func TestCreate(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: string(globals.SshPrivateKeyCredentialType), + CredentialType: string(globals.PasswordCredentialType), AuthorizedActions: testAuthorizedActions, }, }, }, { - name: "Create a valid vault CredentialLibrary ssh_private_key type with mapping", + name: "Create a valid vault CredentialLibrary password type with password mapping", req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), @@ -1119,15 +1053,13 @@ func TestCreate(t *testing.T) { }, CredentialMappingOverrides: func() *structpb.Struct { v := map[string]any{ - usernameAttribute: "user-test", - privateKeyAttribute: "pk-test", - pkPassphraseAttribute: "pass-test", + passwordAttribute: "password-test", } ret, err := structpb.NewStruct(v) require.NoError(t, err) return ret }(), - CredentialType: string(globals.SshPrivateKeyCredentialType), + CredentialType: string(globals.PasswordCredentialType), }}, idPrefix: globals.VaultCredentialLibraryPrefix + "_", res: &pbs.CreateCredentialLibraryResponse{ @@ -1146,12 +1078,10 @@ func TestCreate(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: string(globals.SshPrivateKeyCredentialType), + CredentialType: string(globals.PasswordCredentialType), CredentialMappingOverrides: func() *structpb.Struct { v := map[string]any{ - usernameAttribute: "user-test", - privateKeyAttribute: "pk-test", - pkPassphraseAttribute: "pass-test", + passwordAttribute: "password-test", } ret, err := structpb.NewStruct(v) require.NoError(t, err) @@ -1162,15 +1092,16 @@ func TestCreate(t *testing.T) { }, }, { - name: "Create a valid vault CredentialLibrary with the 'vault' subtype", + name: "Create a valid vault CredentialLibrary ssh_private_key type", req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), - Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{ - VaultCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String("something"), }, }, + CredentialType: string(globals.SshPrivateKeyCredentialType), }}, idPrefix: globals.VaultCredentialLibraryPrefix + "_", res: &pbs.CreateCredentialLibraryResponse{ @@ -1189,26 +1120,79 @@ func TestCreate(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, + CredentialType: string(globals.SshPrivateKeyCredentialType), AuthorizedActions: testAuthorizedActions, }, }, }, { - name: "Create a valid Vault LDAP Credential library", - req: &pbs.CreateCredentialLibraryRequest{ + name: "Create a valid vault CredentialLibrary ssh_private_key type with mapping", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }, + }, + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + usernameAttribute: "user-test", + privateKeyAttribute: "pk-test", + pkPassphraseAttribute: "pass-test", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + CredentialType: string(globals.SshPrivateKeyCredentialType), + }}, + idPrefix: globals.VaultCredentialLibraryPrefix + "_", + res: &pbs.CreateCredentialLibraryResponse{ + Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix), Item: &pb.CredentialLibrary{ + Id: store.GetPublicId(), CredentialStoreId: store.GetPublicId(), - Type: vault.LdapCredentialLibrarySubtype.String(), - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("ldap/creds/hi"), + CreatedTime: store.GetCreateTime().GetTimestamp(), + UpdatedTime: store.GetUpdateTime().GetTimestamp(), + Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, + Version: 1, + Type: vault.GenericLibrarySubtype.String(), + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("GET"), }, }, + CredentialType: string(globals.SshPrivateKeyCredentialType), + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + usernameAttribute: "user-test", + privateKeyAttribute: "pk-test", + pkPassphraseAttribute: "pass-test", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + AuthorizedActions: testAuthorizedActions, }, }, - idPrefix: globals.VaultLdapCredentialLibraryPrefix + "_", + }, + { + name: "Create a valid vault CredentialLibrary with the 'vault' subtype", + req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{ + CredentialStoreId: store.GetPublicId(), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &pb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + }, + }, + }}, + idPrefix: globals.VaultCredentialLibraryPrefix + "_", res: &pbs.CreateCredentialLibraryResponse{ - Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultLdapCredentialLibraryPrefix), + Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix), Item: &pb.CredentialLibrary{ Id: store.GetPublicId(), CredentialStoreId: store.GetPublicId(), @@ -1216,11 +1200,11 @@ func TestCreate(t *testing.T) { UpdatedTime: store.GetUpdateTime().GetTimestamp(), Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()}, Version: 1, - Type: vault.LdapCredentialLibrarySubtype.String(), - CredentialType: string(globals.UsernamePasswordDomainCredentialType), - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("ldap/creds/hi"), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String("something"), + HttpMethod: wrapperspb.String("GET"), }, }, AuthorizedActions: testAuthorizedActions, @@ -1324,6 +1308,17 @@ func TestGet(t *testing.T) { userPassDomainLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libDomain) require.NoError(t, err) + libPassword, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path", + vault.WithCredentialType("password"), + vault.WithMappingOverride( + vault.NewPasswordOverride( + vault.WithOverridePasswordAttribute("pass"), + ))) + + require.NoError(t, err) + passwordLib, err := repo.CreateCredentialLibrary(context.Background(), prj.GetPublicId(), libPassword) + require.NoError(t, err) + lib1, err := vault.NewCredentialLibrary(store.GetPublicId(), "vault/path/ssh", vault.WithCredentialType("ssh_private_key"), vault.WithMappingOverride( @@ -1341,13 +1336,6 @@ func TestGet(t *testing.T) { sshCertLib, err := repo.CreateSSHCertificateCredentialLibrary(context.Background(), prj.GetPublicId(), lib2) require.NoError(t, err) - libLdap1, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/test") - require.NoError(t, err) - require.NotNil(t, libLdap1) - libLdap1, err = repo.CreateLdapCredentialLibrary(t.Context(), prj.GetPublicId(), libLdap1) - require.NoError(t, err) - require.NotNil(t, libLdap1) - cases := []struct { name string id string @@ -1441,6 +1429,37 @@ func TestGet(t *testing.T) { }, }, }, + { + name: "success-password", + id: passwordLib.GetPublicId(), + res: &pbs.GetCredentialLibraryResponse{ + Item: &pb.CredentialLibrary{ + Id: passwordLib.GetPublicId(), + CredentialStoreId: passwordLib.GetStoreId(), + Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, + Type: vault.GenericLibrarySubtype.String(), + AuthorizedActions: testAuthorizedActions, + CreatedTime: passwordLib.CreateTime.GetTimestamp(), + UpdatedTime: passwordLib.UpdateTime.GetTimestamp(), + Version: 1, + Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{ + VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(passwordLib.GetVaultPath()), + HttpMethod: wrapperspb.String(passwordLib.GetHttpMethod()), + }, + }, + CredentialType: "password", + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: "pass", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }, + }, + }, { name: "success-ssh-private-key", id: sshPkLib.GetPublicId(), @@ -1498,28 +1517,6 @@ func TestGet(t *testing.T) { }, }, }, - { - name: "success-ldap", - id: libLdap1.GetPublicId(), - res: &pbs.GetCredentialLibraryResponse{ - Item: &pb.CredentialLibrary{ - Id: libLdap1.GetPublicId(), - CredentialStoreId: libLdap1.GetStoreId(), - CredentialType: libLdap1.GetCredentialType(), - Scope: &scopepb.ScopeInfo{Id: store.GetProjectId(), Type: scope.Project.String(), ParentScopeId: prj.GetParentId()}, - Type: vault.LdapCredentialLibrarySubtype.String(), - AuthorizedActions: testAuthorizedActions, - CreatedTime: libLdap1.CreateTime.GetTimestamp(), - UpdatedTime: libLdap1.UpdateTime.GetTimestamp(), - Version: 1, - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String(libLdap1.GetVaultPath()), - }, - }, - }, - }, - }, { name: "not found error", id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix), @@ -1582,7 +1579,6 @@ func TestDelete(t *testing.T) { store := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1)[0] vl := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), globals.UnspecifiedCredentialType, 1)[0] vl2 := vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] - vl3 := vault.TestLdapCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 1)[0] s, err := NewService(ctx, iamRepoFn, repoFn, 1000) require.NoError(t, err) cases := []struct { @@ -1599,10 +1595,6 @@ func TestDelete(t *testing.T) { name: "success-ssh-cert", id: vl2.GetPublicId(), }, - { - name: "success-ldap", - id: vl3.GetPublicId(), - }, { name: "not found error", id: fmt.Sprintf("%s_1234567890", globals.VaultCredentialLibraryPrefix), @@ -2118,6 +2110,84 @@ func TestUpdate(t *testing.T) { return out }, }, + { + name: "password-attribute-change-password-attribute", + opts: []vault.Option{ + vault.WithCredentialType("password"), + vault.WithMappingOverride( + vault.NewPasswordOverride( + vault.WithOverridePasswordAttribute("orig-pass"), + )), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(passwordAttrField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: "changed-pass", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.CredentialMappingOverrides.Fields[passwordAttribute] = structpb.NewStringValue("changed-pass") + return out + }, + }, + { + name: "password-no-mapping-override-change-password-attribute", + opts: []vault.Option{ + vault.WithCredentialType("password"), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(passwordAttrField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: "new-pass", + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + v := map[string]any{ + passwordAttribute: "new-pass", + } + var err error + out.CredentialMappingOverrides, err = structpb.NewStruct(v) + require.NoError(t, err) + return out + }, + }, + { + name: "password-attribute-delete-mapping-override", + opts: []vault.Option{ + vault.WithCredentialType("password"), + vault.WithMappingOverride( + vault.NewPasswordOverride( + vault.WithOverridePasswordAttribute("orig-pass"), + )), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(credentialMappingPathField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: nil, + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.CredentialMappingOverrides = nil + return out + }, + }, { name: "no-mapping-override-delete-mapping-override", opts: []vault.Option{ @@ -2166,7 +2236,7 @@ func TestUpdate(t *testing.T) { }, }, { - name: "username-password-attributes-delete-mapping-override-field-specific", + name: "username-password-domain-attributes-delete-mapping-override-field-specific", opts: []vault.Option{ vault.WithCredentialType("username_password_domain"), vault.WithMappingOverride( @@ -2248,6 +2318,75 @@ func TestUpdate(t *testing.T) { return out }, }, + { + name: "password-no-mapping-override-delete-mapping-override", + opts: []vault.Option{ + vault.WithCredentialType("password"), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(credentialMappingPathField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: nil, + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.CredentialMappingOverrides = nil + return out + }, + }, + { + name: "password-no-mapping-override-delete-mapping-override-field-specific", + opts: []vault.Option{ + vault.WithCredentialType("password"), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(passwordAttrField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: nil, + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.CredentialMappingOverrides = nil + return out + }, + }, + { + name: "password-delete-mapping-override-field-specific", + opts: []vault.Option{ + vault.WithCredentialType("password"), + vault.WithMappingOverride( + vault.NewPasswordOverride( + vault.WithOverridePasswordAttribute("orig-pass"), + )), + }, + req: &pbs.UpdateCredentialLibraryRequest{ + UpdateMask: fieldmask(passwordAttrField), + Item: &pb.CredentialLibrary{ + CredentialMappingOverrides: func() *structpb.Struct { + v := map[string]any{ + passwordAttribute: nil, + } + ret, err := structpb.NewStruct(v) + require.NoError(t, err) + return ret + }(), + }, + }, + res: func(in *pb.CredentialLibrary) *pb.CredentialLibrary { + out := proto.Clone(in).(*pb.CredentialLibrary) + out.CredentialMappingOverrides = nil + return out + }, + }, { name: "ssh-private-key-attributes-change-username-attribute", opts: []vault.Option{ @@ -3402,287 +3541,6 @@ func TestUpdate_SSHCertificateCredentialLibrary(t *testing.T) { } } -func TestUpdate_LdapCredentialLibrary(t *testing.T) { - testCtx := context.Background() - conn, _ := db.TestSetup(t, "postgres") - wrapper := db.TestWrapper(t) - kms := kms.TestKms(t, conn, wrapper) - sche := scheduler.TestScheduler(t, conn, wrapper) - rw := db.New(conn) - - iamRepo := iam.TestRepo(t, conn, wrapper) - iamRepoFn := func() (*iam.Repository, error) { - return iamRepo, nil - } - repoFn := func() (*vault.Repository, error) { - return vault.NewRepository(testCtx, rw, rw, kms, sche) - } - - _, prj := iam.TestScopes(t, iamRepo) - ctx := auth.DisabledAuthTestContext(iamRepoFn, prj.GetPublicId()) - - s, err := NewService(testCtx, iamRepoFn, repoFn, 1000) - require.NoError(t, err) - cs := vault.TestCredentialStores(t, conn, wrapper, prj.GetPublicId(), 1) - store := cs[0] - - freshLibrary := func(opt ...vault.Option) (*vault.LdapCredentialLibrary, func()) { - repo, err := repoFn() - require.NoError(t, err) - lib, err := vault.NewLdapCredentialLibrary(store.GetPublicId(), "ldap/creds/foo", opt...) - require.NoError(t, err) - - vl, err := repo.CreateLdapCredentialLibrary(ctx, prj.GetPublicId(), lib) - require.NoError(t, err) - clean := func() { - _, err := s.DeleteCredentialLibrary(ctx, &pbs.DeleteCredentialLibraryRequest{Id: vl.GetPublicId()}) - require.NoError(t, err) - } - return vl, clean - } - - tests := []struct { - name string - req *pbs.UpdateCredentialLibraryRequest - res func(*pb.CredentialLibrary) *pb.CredentialLibrary - wantErr bool - wantErrStatus int - wantErrStr string - }{ - { - name: "updateNonExistentField", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"doesntExist"}}, - Item: &pb.CredentialLibrary{ - Name: wrapperspb.String("updateNonExistentField_test"), - }, - }, - wantErr: true, - wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument), - wantErrStr: "No valid fields included in the update mask.", - }, - { - name: "updateImmutableField", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"storeId"}}, - Item: &pb.CredentialLibrary{ - CredentialStoreId: "immutable", - }, - }, - wantErr: true, - wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument), - wantErrStr: "No valid fields included in the update mask.", - }, - { - name: "updateValidFieldWithInvalidData", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}}, - Item: &pb.CredentialLibrary{ - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("invalid_path"), - }, - }, - }, - }, - wantErr: true, - wantErrStr: "vault_path_must_have_staticcred_or_creds constraint failed", - }, - { - name: "updateImmutableCredentialTypeField", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"credentialType"}}, - Item: &pb.CredentialLibrary{ - CredentialType: string(globals.SshPrivateKeyCredentialType), - }, - }, - wantErr: true, - wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument), - wantErrStr: "Cannot modify credential type.", - }, - { - name: "updateImmutableTypeField", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"type"}}, - Item: &pb.CredentialLibrary{ - Type: string(globals.SshCertificateCredentialType), - }, - }, - wantErr: true, - wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument), - wantErrStr: "Cannot modify resource type.", - }, - { - name: "unsetVaultPath", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}}, - Item: &pb.CredentialLibrary{ - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String(""), - }, - }, - }, - }, - wantErr: true, - wantErrStatus: runtime.HTTPStatusFromCode(codes.InvalidArgument), - wantErrStr: "attributes.path\", desc: \"This is a required field and cannot be set to empty.", - }, - { - name: "updateName", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name"}}, - Item: &pb.CredentialLibrary{ - Name: wrapperspb.String("updated_name"), - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.Name = wrapperspb.String("updated_name") - return cl - }, - }, - { - name: "updateDescription", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"description"}}, - Item: &pb.CredentialLibrary{ - Description: wrapperspb.String("updated_description"), - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.Description = wrapperspb.String("updated_description") - return cl - }, - }, - { - name: "updateValidVaultPath", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"attributes.path"}}, - Item: &pb.CredentialLibrary{ - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("ldap/static-cred/updated/path"), - }, - }, - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/static-cred/updated/path") - return cl - }, - }, - { - name: "updateMultipleOneNonExistent", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "doesntExist"}}, - Item: &pb.CredentialLibrary{ - Name: wrapperspb.String("updated_name"), - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.Name = wrapperspb.String("updated_name") - return cl - }, - }, - { - name: "updateMultipleOneImmutable", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime. - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "credentialStoreId"}}, - Item: &pb.CredentialLibrary{ - Name: wrapperspb.String("updated_name"), - CredentialStoreId: "immutable", - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.Name = wrapperspb.String("updated_name") - return cl - }, - }, - { - name: "updateMultiple", - req: &pbs.UpdateCredentialLibraryRequest{ - Id: "", // Set at test runtime - UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"name", "attributes.path"}}, - Item: &pb.CredentialLibrary{ - Name: wrapperspb.String("updated_name"), - Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{ - VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{ - Path: wrapperspb.String("ldap/creds/updated/path"), - }, - }, - }, - }, - res: func(cl *pb.CredentialLibrary) *pb.CredentialLibrary { - cl.Name = wrapperspb.String("updated_name") - cl.GetVaultLdapCredentialLibraryAttributes().Path = wrapperspb.String("ldap/creds/updated/path") - return cl - }, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - assert, require := assert.New(t), require.New(t) - st, cleanup := freshLibrary() - defer cleanup() - - if tc.req.Item.GetVersion() == 0 { - tc.req.Item.Version = 1 - } - if tc.req.GetId() == "" { - tc.req.Id = st.GetPublicId() - } - resToChange, err := s.GetCredentialLibrary(ctx, &pbs.GetCredentialLibraryRequest{Id: st.GetPublicId()}) - require.NoError(err) - - got, gErr := s.UpdateCredentialLibrary(ctx, tc.req) - if tc.wantErr { - require.ErrorContains(gErr, tc.wantErrStr) - - gApiErr, ok := gErr.(*handlers.ApiError) - if ok { - // We don't always return status. - require.NotNil(gApiErr) - require.EqualValues(tc.wantErrStatus, gApiErr.Status) - } - require.Nil(got) - return - } - require.NoError(gErr) - require.NotNil(got) - - want := &pbs.UpdateCredentialLibraryResponse{Item: tc.res(resToChange.GetItem())} - 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(), - cmpopts.SortSlices(func(a, b string) bool { - return a < b - }), - )) - }) - } -} - func TestListPagination(t *testing.T) { // Set database read timeout to avoid duplicates in response oldReadTimeout := globals.RefreshReadLookbackDuration @@ -3731,9 +3589,6 @@ func TestListPagination(t *testing.T) { for _, l := range vault.TestSSHCertificateCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) { allCredentialLibraries = append(allCredentialLibraries, sshCredentialLibraryToProto(l, prj)) } - for _, l := range vault.TestLdapCredentialLibraries(t, conn, wrapper, credStore.GetPublicId(), 5) { - allCredentialLibraries = append(allCredentialLibraries, ldapCredentialLibraryToProto(l, prj)) - } // Reverse as we return items sorted by create_time desceding (newest first) slices.Reverse(allCredentialLibraries) @@ -3787,7 +3642,7 @@ func TestListPagination(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3812,7 +3667,7 @@ func TestListPagination(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3824,10 +3679,10 @@ func TestListPagination(t *testing.T) { // Request rest of results req.ListToken = got.ListToken - req.PageSize = 15 + req.PageSize = 10 got, err = s.ListCredentialLibraries(ctx, req) require.NoError(err) - require.Len(got.GetItems(), 11) + require.Len(got.GetItems(), 6) // Compare without comparing the list token assert.Empty( cmp.Diff( @@ -3838,7 +3693,7 @@ func TestListPagination(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3863,14 +3718,14 @@ func TestListPagination(t *testing.T) { // Update one of the other stores allCredentialLibraries[1].Name = wrapperspb.String("new-name") allCredentialLibraries[1].Version = 2 - updatedLibrary := &vault.LdapCredentialLibrary{ - LdapCredentialLibrary: &store.LdapCredentialLibrary{ + updatedLibrary := &vault.SSHCertificateCredentialLibrary{ + SSHCertificateCredentialLibrary: &store.SSHCertificateCredentialLibrary{ PublicId: allCredentialLibraries[1].GetId(), StoreId: allCredentialLibraries[1].GetCredentialStoreId(), Name: allCredentialLibraries[1].Name.Value, }, } - cred, _, err := vaultRepo.UpdateLdapCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"}) + cred, _, err := vaultRepo.UpdateSSHCertificateCredentialLibrary(ctx, prj.PublicId, updatedLibrary, 1, []string{"name"}) require.NoError(err) allCredentialLibraries[1].UpdatedTime = cred.UpdateTime.GetTimestamp() allCredentialLibraries[1].Version = cred.Version @@ -3904,7 +3759,7 @@ func TestListPagination(t *testing.T) { SortDir: "desc", // Should contain the deleted library RemovedIds: []string{deletedCredLib.Id}, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3929,7 +3784,7 @@ func TestListPagination(t *testing.T) { SortBy: "updated_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3957,7 +3812,7 @@ func TestListPagination(t *testing.T) { SortDir: "desc", // Should be empty again RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b @@ -3980,7 +3835,7 @@ func TestListPagination(t *testing.T) { SortBy: "created_time", SortDir: "desc", RemovedIds: nil, - EstItemCount: 15, + EstItemCount: 10, }, cmpopts.SortSlices(func(a, b string) bool { return a < b diff --git a/internal/daemon/controller/handlers/targets/credentials.go b/internal/daemon/controller/handlers/targets/credentials.go index 5d1a5ae73d..ec3a2c2f75 100644 --- a/internal/daemon/controller/handlers/targets/credentials.go +++ b/internal/daemon/controller/handlers/targets/credentials.go @@ -137,6 +137,17 @@ func dynamicToSessionCredential(ctx context.Context, cred credential.Dynamic) (* return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential")) } + case credential.PasswordOnly: + credData, err = handlers.ProtoToStruct( + ctx, + &pb.PasswordCredential{ + Password: string(c.Password()), + }, + ) + if err != nil { + return nil, errors.Wrap(ctx, err, op, errors.WithMsg("creating proto struct for credential")) + } + case credential.SshPrivateKey: credData, err = handlers.ProtoToStruct( ctx, 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 8354386710..a372d713fa 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -4170,7 +4170,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "username_password", + CredentialType: string(globals.UsernamePasswordCredentialType), }}) require.NoError(t, err) @@ -4203,7 +4203,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "username_password", + CredentialType: string(globals.UsernamePasswordCredentialType), CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("non-default-user"), "password_attribute": structpb.NewStringValue("non-default-pass"), @@ -4211,6 +4211,44 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { }}) require.NoError(t, err) + defaultPassword := v.CreateKVSecret(t, "default-password", []byte(`{"data": {"password": "my-default-password"}}`)) + require.NotNil(t, defaultPassword) + nonDefaultPassword := v.CreateKVSecret(t, "non-default-password", []byte(`{"data": {"non-default-password": "my-non-default-password"}}`)) + require.NotNil(t, nonDefaultPassword) + + clsRespPassword, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), + Name: wrapperspb.String("Password Library"), + Description: wrapperspb.String("Password Library Description"), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(path.Join("secret", "data", "default-password")), + HttpMethod: wrapperspb.String("GET"), + }, + }, + CredentialType: string(globals.PasswordCredentialType), + }}) + require.NoError(t, err) + + clsRespPasswordWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ + CredentialStoreId: vaultStore.GetPublicId(), + Name: wrapperspb.String("Password Mapping Library"), + Description: wrapperspb.String("Password Mapping Library Description"), + Type: vault.GenericLibrarySubtype.String(), + Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ + VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ + Path: wrapperspb.String(path.Join("secret", "data", "non-default-password")), + HttpMethod: wrapperspb.String("GET"), + }, + }, + CredentialType: string(globals.PasswordCredentialType), + CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ + "password_attribute": structpb.NewStringValue("non-default-password"), + }}, + }}) + require.NoError(t, err) + staticStore := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) credService, err := credentials.NewService(ctx, iamRepoFn, staticCredRepoFn, 1000) require.NoError(t, err) @@ -4291,7 +4329,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "ssh_private_key", + CredentialType: string(globals.SshPrivateKeyCredentialType), }}) require.NoError(t, err) @@ -4310,7 +4348,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "ssh_private_key", + CredentialType: string(globals.SshPrivateKeyCredentialType), CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("non-default-user"), "private_key_attribute": structpb.NewStringValue("non-default-pk"), @@ -4333,7 +4371,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "ssh_private_key", + CredentialType: string(globals.SshPrivateKeyCredentialType), }}) require.NoError(t, err) require.NotNil(t, clsRespSshPrivateKeyWithPass) @@ -4353,7 +4391,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { HttpMethod: wrapperspb.String("GET"), }, }, - CredentialType: "ssh_private_key", + CredentialType: string(globals.SshPrivateKeyCredentialType), CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("/data/non-default-user"), "private_key_attribute": structpb.NewStringValue("/data/non-default-pk"), @@ -4443,6 +4481,58 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) { }, wantedConnectionLimit: 100, }, + { + name: "vault-password", + hostSourceId: shs.GetPublicId(), + credSourceId: clsRespPassword.GetItem().GetId(), + wantedHostId: h.GetPublicId(), + wantedEndpoint: h.GetAddress(), + wantedCred: &pb.SessionCredential{ + CredentialSource: &pb.CredentialSource{ + Id: clsRespPassword.GetItem().GetId(), + Name: clsRespPassword.GetItem().GetName().GetValue(), + Description: clsRespPassword.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.GenericLibrarySubtype.String(), + CredentialType: string(globals.PasswordCredentialType), + }, + Credential: func() *structpb.Struct { + data := map[string]any{ + "password": "my-default-password", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, + wantedConnectionLimit: 10, + }, + { + name: "vault-password-with-mapping", + hostSourceId: shs.GetPublicId(), + credSourceId: clsRespPasswordWithMapping.GetItem().GetId(), + wantedHostId: h.GetPublicId(), + wantedEndpoint: h.GetAddress(), + wantedCred: &pb.SessionCredential{ + CredentialSource: &pb.CredentialSource{ + Id: clsRespPasswordWithMapping.GetItem().GetId(), + Name: clsRespPasswordWithMapping.GetItem().GetName().GetValue(), + Description: clsRespPasswordWithMapping.GetItem().GetDescription().GetValue(), + CredentialStoreId: vaultStore.GetPublicId(), + Type: vault.GenericLibrarySubtype.String(), + CredentialType: string(globals.PasswordCredentialType), + }, + Credential: func() *structpb.Struct { + data := map[string]any{ + "password": "my-non-default-password", + } + st, err := structpb.NewStruct(data) + require.NoError(t, err) + return st + }(), + }, + wantedConnectionLimit: 100, + }, { name: "static-UsernamePassword", hostSourceId: shs.GetPublicId(), diff --git a/internal/proto/controller/api/resources/targets/v1/target.proto b/internal/proto/controller/api/resources/targets/v1/target.proto index 62d111bdc1..975029bbb8 100644 --- a/internal/proto/controller/api/resources/targets/v1/target.proto +++ b/internal/proto/controller/api/resources/targets/v1/target.proto @@ -475,6 +475,12 @@ message UsernamePasswordDomainCredential { string domain = 3; // @gotags: `class:"public"` } +// The layout of the struct for "credential" field in SessionCredential for a password credential type. +message PasswordCredential { + // Password of the credential + string password = 1; // @gotags: `class:"secret"` +} + // The layout of the struct for "credential" field in SessionCredential for a ssh_private_key credential type. message SshPrivateKeyCredential { // Username of the credential diff --git a/sdk/pbs/controller/api/resources/targets/target.pb.go b/sdk/pbs/controller/api/resources/targets/target.pb.go index dbfe433365..b7547a1bef 100644 --- a/sdk/pbs/controller/api/resources/targets/target.pb.go +++ b/sdk/pbs/controller/api/resources/targets/target.pb.go @@ -1487,6 +1487,52 @@ func (x *UsernamePasswordDomainCredential) GetDomain() string { return "" } +// The layout of the struct for "credential" field in SessionCredential for a password credential type. +type PasswordCredential struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Password of the credential + Password string `protobuf:"bytes,1,opt,name=password,proto3" json:"password,omitempty" class:"secret"` // @gotags: `class:"secret"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PasswordCredential) Reset() { + *x = PasswordCredential{} + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PasswordCredential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PasswordCredential) ProtoMessage() {} + +func (x *PasswordCredential) ProtoReflect() protoreflect.Message { + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PasswordCredential.ProtoReflect.Descriptor instead. +func (*PasswordCredential) Descriptor() ([]byte, []int) { + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{16} +} + +func (x *PasswordCredential) GetPassword() string { + if x != nil { + return x.Password + } + return "" +} + // The layout of the struct for "credential" field in SessionCredential for a ssh_private_key credential type. type SshPrivateKeyCredential struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1502,7 +1548,7 @@ type SshPrivateKeyCredential struct { func (x *SshPrivateKeyCredential) Reset() { *x = SshPrivateKeyCredential{} - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1514,7 +1560,7 @@ func (x *SshPrivateKeyCredential) String() string { func (*SshPrivateKeyCredential) ProtoMessage() {} func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message { - mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[16] + mi := &file_controller_api_resources_targets_v1_target_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1527,7 +1573,7 @@ func (x *SshPrivateKeyCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use SshPrivateKeyCredential.ProtoReflect.Descriptor instead. func (*SshPrivateKeyCredential) Descriptor() ([]byte, []int) { - return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{16} + return file_controller_api_resources_targets_v1_target_proto_rawDescGZIP(), []int{17} } func (x *SshPrivateKeyCredential) GetUsername() string { @@ -1709,7 +1755,9 @@ const file_controller_api_resources_targets_v1_target_proto_rawDesc = "" + " UsernamePasswordDomainCredential\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12\x1a\n" + "\bpassword\x18\x02 \x01(\tR\bpassword\x12\x16\n" + - "\x06domain\x18\x03 \x01(\tR\x06domain\"\x8c\x01\n" + + "\x06domain\x18\x03 \x01(\tR\x06domain\"0\n" + + "\x12PasswordCredential\x12\x1a\n" + + "\bpassword\x18\x01 \x01(\tR\bpassword\"\x8c\x01\n" + "\x17SshPrivateKeyCredential\x12\x1a\n" + "\busername\x18\x01 \x01(\tR\busername\x12\x1f\n" + "\vprivate_key\x18\x02 \x01(\tR\n" + @@ -1728,7 +1776,7 @@ func file_controller_api_resources_targets_v1_target_proto_rawDescGZIP() []byte return file_controller_api_resources_targets_v1_target_proto_rawDescData } -var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 17) +var file_controller_api_resources_targets_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 18) var file_controller_api_resources_targets_v1_target_proto_goTypes = []any{ (*Alias)(nil), // 0: controller.api.resources.targets.v1.Alias (*TargetAliasAttributes)(nil), // 1: controller.api.resources.targets.v1.TargetAliasAttributes @@ -1746,57 +1794,58 @@ var file_controller_api_resources_targets_v1_target_proto_goTypes = []any{ (*SessionAuthorization)(nil), // 13: controller.api.resources.targets.v1.SessionAuthorization (*UsernamePasswordCredential)(nil), // 14: controller.api.resources.targets.v1.UsernamePasswordCredential (*UsernamePasswordDomainCredential)(nil), // 15: controller.api.resources.targets.v1.UsernamePasswordDomainCredential - (*SshPrivateKeyCredential)(nil), // 16: controller.api.resources.targets.v1.SshPrivateKeyCredential - (*structpb.Struct)(nil), // 17: google.protobuf.Struct - (*scopes.ScopeInfo)(nil), // 18: controller.api.resources.scopes.v1.ScopeInfo - (*wrapperspb.StringValue)(nil), // 19: google.protobuf.StringValue - (*timestamppb.Timestamp)(nil), // 20: google.protobuf.Timestamp - (*wrapperspb.UInt32Value)(nil), // 21: google.protobuf.UInt32Value - (*wrapperspb.Int32Value)(nil), // 22: google.protobuf.Int32Value - (*wrapperspb.BoolValue)(nil), // 23: google.protobuf.BoolValue + (*PasswordCredential)(nil), // 16: controller.api.resources.targets.v1.PasswordCredential + (*SshPrivateKeyCredential)(nil), // 17: controller.api.resources.targets.v1.SshPrivateKeyCredential + (*structpb.Struct)(nil), // 18: google.protobuf.Struct + (*scopes.ScopeInfo)(nil), // 19: controller.api.resources.scopes.v1.ScopeInfo + (*wrapperspb.StringValue)(nil), // 20: google.protobuf.StringValue + (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp + (*wrapperspb.UInt32Value)(nil), // 22: google.protobuf.UInt32Value + (*wrapperspb.Int32Value)(nil), // 23: google.protobuf.Int32Value + (*wrapperspb.BoolValue)(nil), // 24: google.protobuf.BoolValue } var file_controller_api_resources_targets_v1_target_proto_depIdxs = []int32{ 1, // 0: controller.api.resources.targets.v1.Alias.attributes:type_name -> controller.api.resources.targets.v1.TargetAliasAttributes 2, // 1: controller.api.resources.targets.v1.TargetAliasAttributes.authorize_session_arguments:type_name -> controller.api.resources.targets.v1.AuthorizeSessionArguments - 17, // 2: controller.api.resources.targets.v1.SessionSecret.decoded:type_name -> google.protobuf.Struct + 18, // 2: controller.api.resources.targets.v1.SessionSecret.decoded:type_name -> google.protobuf.Struct 4, // 3: controller.api.resources.targets.v1.SessionCredential.credential_source:type_name -> controller.api.resources.targets.v1.CredentialSource 5, // 4: controller.api.resources.targets.v1.SessionCredential.secret:type_name -> controller.api.resources.targets.v1.SessionSecret - 17, // 5: controller.api.resources.targets.v1.SessionCredential.credential:type_name -> google.protobuf.Struct - 18, // 6: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 19, // 7: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue - 19, // 8: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue - 20, // 9: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp - 20, // 10: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp + 18, // 5: controller.api.resources.targets.v1.SessionCredential.credential:type_name -> google.protobuf.Struct + 19, // 6: controller.api.resources.targets.v1.Target.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 20, // 7: controller.api.resources.targets.v1.Target.name:type_name -> google.protobuf.StringValue + 20, // 8: controller.api.resources.targets.v1.Target.description:type_name -> google.protobuf.StringValue + 21, // 9: controller.api.resources.targets.v1.Target.created_time:type_name -> google.protobuf.Timestamp + 21, // 10: controller.api.resources.targets.v1.Target.updated_time:type_name -> google.protobuf.Timestamp 3, // 11: controller.api.resources.targets.v1.Target.host_sources:type_name -> controller.api.resources.targets.v1.HostSource - 21, // 12: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value - 22, // 13: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value - 19, // 14: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue - 19, // 15: controller.api.resources.targets.v1.Target.egress_worker_filter:type_name -> google.protobuf.StringValue - 19, // 16: controller.api.resources.targets.v1.Target.ingress_worker_filter:type_name -> google.protobuf.StringValue + 22, // 12: controller.api.resources.targets.v1.Target.session_max_seconds:type_name -> google.protobuf.UInt32Value + 23, // 13: controller.api.resources.targets.v1.Target.session_connection_limit:type_name -> google.protobuf.Int32Value + 20, // 14: controller.api.resources.targets.v1.Target.worker_filter:type_name -> google.protobuf.StringValue + 20, // 15: controller.api.resources.targets.v1.Target.egress_worker_filter:type_name -> google.protobuf.StringValue + 20, // 16: controller.api.resources.targets.v1.Target.ingress_worker_filter:type_name -> google.protobuf.StringValue 4, // 17: controller.api.resources.targets.v1.Target.brokered_credential_sources:type_name -> controller.api.resources.targets.v1.CredentialSource 4, // 18: controller.api.resources.targets.v1.Target.injected_application_credential_sources:type_name -> controller.api.resources.targets.v1.CredentialSource - 17, // 19: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct + 18, // 19: controller.api.resources.targets.v1.Target.attributes:type_name -> google.protobuf.Struct 8, // 20: controller.api.resources.targets.v1.Target.tcp_target_attributes:type_name -> controller.api.resources.targets.v1.TcpTargetAttributes 9, // 21: controller.api.resources.targets.v1.Target.ssh_target_attributes:type_name -> controller.api.resources.targets.v1.SshTargetAttributes 10, // 22: controller.api.resources.targets.v1.Target.rdp_target_attributes:type_name -> controller.api.resources.targets.v1.RdpTargetAttributes - 19, // 23: controller.api.resources.targets.v1.Target.address:type_name -> google.protobuf.StringValue + 20, // 23: controller.api.resources.targets.v1.Target.address:type_name -> google.protobuf.StringValue 0, // 24: controller.api.resources.targets.v1.Target.aliases:type_name -> controller.api.resources.targets.v1.Alias 0, // 25: controller.api.resources.targets.v1.Target.with_aliases:type_name -> controller.api.resources.targets.v1.Alias - 21, // 26: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value - 21, // 27: controller.api.resources.targets.v1.TcpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value - 21, // 28: controller.api.resources.targets.v1.SshTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value - 21, // 29: controller.api.resources.targets.v1.SshTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value - 19, // 30: controller.api.resources.targets.v1.SshTargetAttributes.storage_bucket_id:type_name -> google.protobuf.StringValue - 23, // 31: controller.api.resources.targets.v1.SshTargetAttributes.enable_session_recording:type_name -> google.protobuf.BoolValue - 21, // 32: controller.api.resources.targets.v1.RdpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value - 21, // 33: controller.api.resources.targets.v1.RdpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value - 18, // 34: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 20, // 35: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp - 20, // 36: controller.api.resources.targets.v1.SessionAuthorizationData.expiration:type_name -> google.protobuf.Timestamp + 22, // 26: controller.api.resources.targets.v1.TcpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value + 22, // 27: controller.api.resources.targets.v1.TcpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value + 22, // 28: controller.api.resources.targets.v1.SshTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value + 22, // 29: controller.api.resources.targets.v1.SshTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value + 20, // 30: controller.api.resources.targets.v1.SshTargetAttributes.storage_bucket_id:type_name -> google.protobuf.StringValue + 24, // 31: controller.api.resources.targets.v1.SshTargetAttributes.enable_session_recording:type_name -> google.protobuf.BoolValue + 22, // 32: controller.api.resources.targets.v1.RdpTargetAttributes.default_port:type_name -> google.protobuf.UInt32Value + 22, // 33: controller.api.resources.targets.v1.RdpTargetAttributes.default_client_port:type_name -> google.protobuf.UInt32Value + 19, // 34: controller.api.resources.targets.v1.SessionAuthorizationData.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 21, // 35: controller.api.resources.targets.v1.SessionAuthorizationData.created_time:type_name -> google.protobuf.Timestamp + 21, // 36: controller.api.resources.targets.v1.SessionAuthorizationData.expiration:type_name -> google.protobuf.Timestamp 11, // 37: controller.api.resources.targets.v1.SessionAuthorizationData.worker_info:type_name -> controller.api.resources.targets.v1.WorkerInfo - 18, // 38: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo - 20, // 39: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp - 20, // 40: controller.api.resources.targets.v1.SessionAuthorization.expiration:type_name -> google.protobuf.Timestamp + 19, // 38: controller.api.resources.targets.v1.SessionAuthorization.scope:type_name -> controller.api.resources.scopes.v1.ScopeInfo + 21, // 39: controller.api.resources.targets.v1.SessionAuthorization.created_time:type_name -> google.protobuf.Timestamp + 21, // 40: controller.api.resources.targets.v1.SessionAuthorization.expiration:type_name -> google.protobuf.Timestamp 6, // 41: controller.api.resources.targets.v1.SessionAuthorization.credentials:type_name -> controller.api.resources.targets.v1.SessionCredential 42, // [42:42] is the sub-list for method output_type 42, // [42:42] is the sub-list for method input_type @@ -1822,7 +1871,7 @@ func file_controller_api_resources_targets_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_controller_api_resources_targets_v1_target_proto_rawDesc), len(file_controller_api_resources_targets_v1_target_proto_rawDesc)), NumEnums: 0, - NumMessages: 17, + NumMessages: 18, NumExtensions: 0, NumServices: 0, },