feat(handlers): Implement CRUDL for vault generic Password credential (#6161)

This commit is contained in:
Justin Nguyen authored and Bharath Gajjala committed 2025-11-13 12:28:04 -08:00
1 parent 4a8009fec4
commit 82745e16be
8 files changed
+546 -504

No files matched your search

@@ -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 {
@@ -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(""),
},
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 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"),
},
},
},
},
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),
},
@@ -1073,6 +1007,90 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid vault CredentialLibrary password type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
},
},
CredentialType: string(globals.PasswordCredentialType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary password type with 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"),
},
},
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "password-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
CredentialType: string(globals.PasswordCredentialType),
}},
idPrefix: globals.VaultCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.GenericLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultGenericCredentialLibraryAttributes{
VaultGenericCredentialLibraryAttributes: &pb.VaultCredentialLibraryAttributes{
Path: wrapperspb.String("something"),
HttpMethod: wrapperspb.String("GET"),
},
},
CredentialType: string(globals.PasswordCredentialType),
CredentialMappingOverrides: func() *structpb.Struct {
v := map[string]any{
passwordAttribute: "password-test",
}
ret, err := structpb.NewStruct(v)
require.NoError(t, err)
return ret
}(),
AuthorizedActions: testAuthorizedActions,
},
},
},
{
name: "Create a valid vault CredentialLibrary ssh_private_key type",
req: &pbs.CreateCredentialLibraryRequest{Item: &pb.CredentialLibrary{
@@ -1193,40 +1211,6 @@ func TestCreate(t *testing.T) {
},
},
},
{
name: "Create a valid Vault LDAP Credential library",
req: &pbs.CreateCredentialLibraryRequest{
Item: &pb.CredentialLibrary{
CredentialStoreId: store.GetPublicId(),
Type: vault.LdapCredentialLibrarySubtype.String(),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
},
},
idPrefix: globals.VaultLdapCredentialLibraryPrefix + "_",
res: &pbs.CreateCredentialLibraryResponse{
Uri: fmt.Sprintf("credential-libraries/%s_", globals.VaultLdapCredentialLibraryPrefix),
Item: &pb.CredentialLibrary{
Id: store.GetPublicId(),
CredentialStoreId: store.GetPublicId(),
CreatedTime: store.GetCreateTime().GetTimestamp(),
UpdatedTime: store.GetUpdateTime().GetTimestamp(),
Scope: &scopepb.ScopeInfo{Id: prj.GetPublicId(), Type: prj.GetType(), ParentScopeId: prj.GetParentId()},
Version: 1,
Type: vault.LdapCredentialLibrarySubtype.String(),
CredentialType: string(globals.UsernamePasswordDomainCredentialType),
Attrs: &pb.CredentialLibrary_VaultLdapCredentialLibraryAttributes{
VaultLdapCredentialLibraryAttributes: &pb.VaultLdapCredentialLibraryAttributes{
Path: wrapperspb.String("ldap/creds/hi"),
},
},
AuthorizedActions: testAuthorizedActions,
},
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
@@ -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
@@ -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,
@@ -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(),