From 00dfea1244edab4110a9b6e8ec9dbd4744ad730d Mon Sep 17 00:00:00 2001 From: Louis Ruch Date: Wed, 8 Jun 2022 13:51:56 -0700 Subject: [PATCH] feat(target): Add support for static credential as target credential sources --- .../handlers/targets/target_service.go | 83 ++- .../targets/tcp/target_service_test.go | 238 +++++++-- .../oss/postgres/10/05_target.up.sql | 1 + .../oss/postgres/50/02_target.up.sql | 80 +++ .../storage/target/store/v1/target.proto | 46 ++ internal/session/repository_session_test.go | 9 +- internal/target/credential.go | 68 +++ internal/target/credential_library.go | 37 -- internal/target/credential_source.go | 61 ++- internal/target/credential_test.go | 69 +++ internal/target/options.go | 9 + internal/target/options_test.go | 33 ++ internal/target/query.go | 51 +- internal/target/registry.go | 17 +- .../target/repository_credential_source.go | 385 +++++++++----- .../repository_credential_source_test.go | 193 ++++--- internal/target/store/target.pb.go | 331 +++++++++++- internal/target/targettest/target.go | 4 +- internal/target/tcp/register.go | 15 +- .../tcp/repository_credential_source_test.go | 473 ++++++++++++------ .../target/tcp/repository_tcp_target_test.go | 2 +- internal/target/tcp/testing.go | 9 + internal/target/tcp/testing_test.go | 4 +- internal/target/testing.go | 13 + internal/tests/api/targets/target_test.go | 82 ++- .../boundary/_target_credential_sources.bash | 52 ++ .../boundary/target_credential_sources.bats | 174 +++++++ 27 files changed, 1951 insertions(+), 588 deletions(-) create mode 100644 internal/db/schema/migrations/oss/postgres/50/02_target.up.sql create mode 100644 internal/target/credential.go create mode 100644 internal/target/credential_test.go create mode 100644 internal/tests/cli/boundary/_target_credential_sources.bash create mode 100644 internal/tests/cli/boundary/target_credential_sources.bats diff --git a/internal/daemon/controller/handlers/targets/target_service.go b/internal/daemon/controller/handlers/targets/target_service.go index f27c75a78..69fb4b0d0 100644 --- a/internal/daemon/controller/handlers/targets/target_service.go +++ b/internal/daemon/controller/handlers/targets/target_service.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/boundary/globals" "github.com/hashicorp/boundary/internal/credential" + credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/common" @@ -1443,12 +1444,16 @@ func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string if err != nil { return nil, nil, nil, err } - credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds) - if err != nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + + var creds target.CredentialSources + if len(applicationIds) > 0 { + creds.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false) + } + if len(egressIds) > 0 { + creds.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false) } - out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, credLibs) + out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, creds) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to add credential sources to target: %v.", err) @@ -1466,12 +1471,15 @@ func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string return nil, nil, nil, err } - credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds) - if err != nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + var ids target.CredentialSources + if len(applicationIds) > 0 { + ids.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false) + } + if len(egressIds) > 0 { + ids.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false) } - _, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, credLibs) + _, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, ids) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) @@ -1494,11 +1502,14 @@ func (s Service) removeCredentialSourcesInRepo(ctx context.Context, targetId str return nil, nil, nil, err } - credLibs, err := createCredLibs(targetId, applicationIds, nil, egressIds) - if err != nil { - return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + var ids target.CredentialSources + if len(applicationIds) > 0 { + ids.ApplicationCredentialIds = strutil.RemoveDuplicates(applicationIds, false) } - _, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, credLibs) + if len(egressIds) > 0 { + ids.EgressCredentialIds = strutil.RemoveDuplicates(egressIds, false) + } + _, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, ids) if err != nil { // TODO: Figure out a way to surface more helpful error info beyond the Internal error. return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to remove credential sources from target: %v.", err) @@ -1644,6 +1655,7 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou var appCredSources, egressCredSources []*pb.CredentialSource var appCredSourceIds, egressCredSourceIds []string var appCredLibraries []*pb.CredentialLibrary + var appCredLibIds []string for _, cs := range credSources { switch cs.CredentialPurpose() { @@ -1655,11 +1667,14 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou }) // ApplicationCredentialLibrariesField is deprecated and should only be populated - // for application purpose - appCredLibraries = append(appCredLibraries, &pb.CredentialLibrary{ - Id: cs.Id(), - CredentialStoreId: cs.CredentialStoreId(), - }) + // for library type sources + if cs.Type() == "library" { + appCredLibIds = append(appCredLibIds, cs.Id()) + appCredLibraries = append(appCredLibraries, &pb.CredentialLibrary{ + Id: cs.Id(), + CredentialStoreId: cs.CredentialStoreId(), + }) + } case credential.EgressPurpose: egressCredSources = append(egressCredSources, &pb.CredentialSource{ @@ -1674,7 +1689,7 @@ func toProto(ctx context.Context, in target.Target, hostSources []target.HostSou } if outputFields.Has(globals.ApplicationCredentialLibraryIdsField) { - out.ApplicationCredentialLibraryIds = appCredSourceIds + out.ApplicationCredentialLibraryIds = appCredLibIds } if outputFields.Has(globals.ApplicationCredentialSourceIdsField) { out.ApplicationCredentialSourceIds = appCredSourceIds @@ -2040,13 +2055,13 @@ func validateAddCredentialSourcesRequest(req *pbs.AddTargetCredentialSourcesRequ badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided." } for _, cl := range req.GetApplicationCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetEgressCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } @@ -2066,13 +2081,13 @@ func validateSetCredentialSourcesRequest(req *pbs.SetTargetCredentialSourcesRequ badFields[globals.VersionField] = "Required field." } for _, cl := range req.GetApplicationCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetEgressCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } @@ -2096,13 +2111,13 @@ func validateRemoveCredentialSourcesRequest(req *pbs.RemoveTargetCredentialSourc badFields[globals.EgressCredentialSourceIdsField] = "Application or Egress Credential Source IDs must be provided." } for _, cl := range req.GetApplicationCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.ApplicationCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } } for _, cl := range req.GetEgressCredentialSourceIds() { - if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix) { + if !handlers.ValidId(handlers.Id(cl), vault.CredentialLibraryPrefix, credstatic.CredentialPrefix) { badFields[globals.EgressCredentialSourceIdsField] = fmt.Sprintf("Incorrectly formatted credential source identifier %q.", cl) break } @@ -2154,26 +2169,6 @@ func validateAuthorizeSessionRequest(req *pbs.AuthorizeSessionRequest) error { return nil } -func createCredLibs(targetId string, applicationIds, ingressIds, egressIds []string) ([]*target.CredentialLibrary, error) { - credLibs := make([]*target.CredentialLibrary, 0, len(applicationIds)+len(ingressIds)+len(egressIds)) - - byPurpose := map[credential.Purpose][]string{ - credential.ApplicationPurpose: strutil.RemoveDuplicates(applicationIds, false), - credential.IngressPurpose: strutil.RemoveDuplicates(ingressIds, false), - credential.EgressPurpose: strutil.RemoveDuplicates(egressIds, false), - } - for purpose, ids := range byPurpose { - for _, id := range ids { - l, err := target.NewCredentialLibrary(targetId, id, purpose) - if err != nil { - return nil, err - } - credLibs = append(credLibs, l) - } - } - return credLibs, nil -} - // credentialToProto converts the strongly typed credential.Credential into a known proto.Message. func credentialToProto(ctx context.Context, cred credential.Credential) (*serverpb.Credential, error) { const op = "targets.credentialToProto" 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 36a061dab..7e57aa99d 100644 --- a/internal/daemon/controller/handlers/targets/tcp/target_service_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/target_service_test.go @@ -15,6 +15,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/credential" + credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" @@ -2241,7 +2242,7 @@ func TestRemoveTargetLibraries(t *testing.T) { } } -func TestAddTargetSources(t *testing.T) { +func TestAddTargetCredentialSources(t *testing.T) { conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) @@ -2256,8 +2257,11 @@ func TestAddTargetSources(t *testing.T) { s, err := testService(t, conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") - store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] - cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) + + storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) + creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) ctx := context.Background() addCases := []struct { @@ -2265,24 +2269,61 @@ func TestAddTargetSources(t *testing.T) { tar target.Target addSources []string resultSourceIds []string + resultLibIds []string }{ { - name: "Add source on empty target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for sources"), + name: "Add library on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for lib sources"), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, }, { - name: "Add source on populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + name: "Add static cred on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for static sources"), + addSources: []string{creds[1].GetPublicId()}, + resultSourceIds: []string{creds[1].GetPublicId()}, + }, + { + name: "Add library on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + resultLibIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, { - name: "Add duplicated sources on populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), - addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, - resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + name: "Add library on static cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + addSources: []string{cls[1].GetPublicId()}, + resultSourceIds: []string{creds[0].GetPublicId(), cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Add static cred on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + addSources: []string{creds[1].GetPublicId()}, + resultSourceIds: []string{cls[0].GetPublicId(), creds[1].GetPublicId()}, + resultLibIds: []string{cls[0].GetPublicId()}, + }, + { + name: "Add static cred on static cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + addSources: []string{creds[1].GetPublicId()}, + resultSourceIds: []string{creds[0].GetPublicId(), creds[1].GetPublicId()}, + }, + { + name: "Add duplicated sources on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId(), creds[1].GetPublicId()}, + resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId()}, + resultLibIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, + }, + { + name: "Add duplicated sources on static cred populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId(), creds[1].GetPublicId()}, + resultSourceIds: []string{creds[0].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, }, } @@ -2298,16 +2339,10 @@ func TestAddTargetSources(t *testing.T) { require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultSourceIds, got.GetItem().GetApplicationCredentialSourceIds()) - assert.Equal(t, len(tc.resultSourceIds), len(got.GetItem().GetApplicationCredentialSources())) - wantTemplate := &pb.CredentialSource{ - CredentialStoreId: store.GetPublicId(), - } - for _, cl := range got.GetItem().GetApplicationCredentialSources() { - cl.Id = "" - assert.Empty(t, cmp.Diff(wantTemplate, cl, protocmp.Transform())) - } + assert.ElementsMatch(t, tc.resultLibIds, got.GetItem().GetApplicationCredentialLibraryIds()) + assert.Equal(t, len(tc.resultLibIds), len(got.GetItem().GetApplicationCredentialLibraries())) }) } @@ -2385,15 +2420,11 @@ func TestSetTargetCredentialSources(t *testing.T) { s, err := testService(t, conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") - store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] - cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) - resultingTargetSource := func(id string) *pb.CredentialSource { - return &pb.CredentialSource{ - Id: id, - CredentialStoreId: store.GetPublicId(), - } - } + storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) + creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) ctx := context.Background() setCases := []struct { @@ -2402,28 +2433,65 @@ func TestSetTargetCredentialSources(t *testing.T) { setCredentialSources []string resultCredentialSourceIds []string resultCredentialSources []*pb.CredentialLibrary + resultLibIds []string }{ { - name: "Set on empty target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty"), + name: "Set libary on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty libary"), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, }, { - name: "Set on populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + name: "Set static on empty target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static"), + setCredentialSources: []string{creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[1].GetPublicId()}, + }, + { + name: "Set library on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated libary-libary", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, }, { - name: "Set duplicate sources on populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + name: "Set static on library populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-libary", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + setCredentialSources: []string{creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[1].GetPublicId()}, + }, + { + name: "Set library on static populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + setCredentialSources: []string{cls[1].GetPublicId()}, + resultCredentialSourceIds: []string{cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, + }, + { + name: "Set static on static populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + setCredentialSources: []string{creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[1].GetPublicId()}, + }, + { + name: "Set duplicate library on populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, + resultLibIds: []string{cls[1].GetPublicId()}, }, { - name: "Set empty on populated target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), + name: "Set duplicate static on populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), + setCredentialSources: []string{creds[1].GetPublicId(), creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[1].GetPublicId()}, + }, + { + name: "Set empty on populated target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated", + target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)}), + target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{}, resultCredentialSourceIds: nil, }, @@ -2450,9 +2518,7 @@ func TestSetTargetCredentialSources(t *testing.T) { assert.Empty(t, cmp.Diff(tc.resultCredentialSources, got.GetItem().GetApplicationCredentialSourceIds(), protocmp.Transform())) } else { assert.Equal(t, len(tc.resultCredentialSourceIds), len(got.GetItem().GetApplicationCredentialSourceIds())) - for _, cl := range got.GetItem().GetApplicationCredentialSources() { - assert.Empty(t, cmp.Diff(resultingTargetSource(cl.GetId()), cl, protocmp.Transform())) - } + assert.Equal(t, len(tc.resultLibIds), len(got.GetItem().GetApplicationCredentialLibraries())) } }) } @@ -2519,8 +2585,11 @@ func TestRemoveTargetCredentialSources(t *testing.T) { s, err := testService(t, conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") - store := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] - cls := vault.TestCredentialLibraries(t, conn, wrapper, store.GetPublicId(), 2) + csVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + cls := vault.TestCredentialLibraries(t, conn, wrapper, csVault.GetPublicId(), 2) + + csStatic := credstatic.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", csStatic.GetPublicId(), proj.GetPublicId(), 2) ctx := context.Background() removeCases := []struct { @@ -2529,32 +2598,99 @@ func TestRemoveTargetCredentialSources(t *testing.T) { removeCredentialSources []string resultCredentialSourceIds []string wantErr bool + resultLibIds []string }{ { - name: "Remove from empty", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty"), + name: "Remove library from empty", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty lib"), removeCredentialSources: []string{cls[1].GetPublicId()}, wantErr: true, }, { - name: "Remove 1 of 2 sources", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), - removeCredentialSources: []string{cls[1].GetPublicId()}, - resultCredentialSourceIds: []string{cls[0].GetPublicId()}, + name: "Remove static from empty", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static"), + removeCredentialSources: []string{creds[1].GetPublicId()}, + wantErr: true, }, { - name: "Remove 1 duplicate set of 2 sources", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), + name: "Remove 1 of 2 libraries", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial lib", + target.WithCredentialLibraries([]*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose), + })), + removeCredentialSources: []string{cls[1].GetPublicId()}, + resultCredentialSourceIds: []string{cls[0].GetPublicId()}, + resultLibIds: []string{cls[0].GetPublicId()}, + }, + { + name: "Remove 1 of 2 static credentials", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial static", + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.ApplicationPurpose), + })), + removeCredentialSources: []string{creds[1].GetPublicId()}, + resultCredentialSourceIds: []string{creds[0].GetPublicId()}, + }, + { + name: "Remove 1 duplicate set of 2 libraries", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate lib", + target.WithCredentialLibraries([]*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose), + })), removeCredentialSources: []string{ cls[1].GetPublicId(), cls[1].GetPublicId(), }, resultCredentialSourceIds: []string{cls[0].GetPublicId()}, + resultLibIds: []string{cls[0].GetPublicId()}, + }, + { + name: "Remove 1 duplicate set of 2 static credentials", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate static", + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.ApplicationPurpose), + })), + removeCredentialSources: []string{ + creds[1].GetPublicId(), creds[1].GetPublicId(), + }, + resultCredentialSourceIds: []string{creds[0].GetPublicId()}, + }, + { + name: "Remove mixed sources from target", + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove mixed", + target.WithCredentialLibraries([]*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose), + }), + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.ApplicationPurpose), + })), + removeCredentialSources: []string{ + cls[1].GetPublicId(), creds[0].GetPublicId(), + }, + resultCredentialSourceIds: []string{ + cls[0].GetPublicId(), creds[1].GetPublicId(), + }, + resultLibIds: []string{cls[0].GetPublicId()}, }, { name: "Remove all sources from target", - tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove all", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), + tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove all", + target.WithCredentialLibraries([]*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose), + }), + target.WithStaticCredentials([]*target.StaticCredential{ + target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.ApplicationPurpose), + })), removeCredentialSources: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), + creds[0].GetPublicId(), creds[1].GetPublicId(), }, resultCredentialSourceIds: []string{}, }, @@ -2576,6 +2712,10 @@ func TestRemoveTargetCredentialSources(t *testing.T) { require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultCredentialSourceIds, got.GetItem().GetApplicationCredentialSourceIds()) + assert.Equal(t, len(tc.resultCredentialSourceIds), len(got.GetItem().GetApplicationCredentialSources())) + + assert.ElementsMatch(t, tc.resultLibIds, got.GetItem().GetApplicationCredentialLibraryIds()) + assert.Equal(t, len(tc.resultLibIds), len(got.GetItem().GetApplicationCredentialLibraries())) }) } diff --git a/internal/db/schema/migrations/oss/postgres/10/05_target.up.sql b/internal/db/schema/migrations/oss/postgres/10/05_target.up.sql index 58e4b97ce..02bab05dc 100644 --- a/internal/db/schema/migrations/oss/postgres/10/05_target.up.sql +++ b/internal/db/schema/migrations/oss/postgres/10/05_target.up.sql @@ -29,6 +29,7 @@ begin; create trigger immutable_columns before update on target_credential_library for each row execute procedure immutable_columns('target_id', 'credential_library_id', 'credential_purpose', 'create_time'); + -- replaced in 50/02_target.up.sql -- target_library provides the store id along with the other data stored in -- target_credential_library create view target_library diff --git a/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql b/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql new file mode 100644 index 000000000..c297565f7 --- /dev/null +++ b/internal/db/schema/migrations/oss/postgres/50/02_target.up.sql @@ -0,0 +1,80 @@ +begin; + + -- target_static_credential does not follow the normal table naming convention of target_credential_static + create table target_static_credential ( + target_id wt_public_id not null + constraint target_fkey + references target (public_id) + on delete cascade + on update cascade, + credential_static_id wt_public_id not null + constraint credential_static_fkey + references credential_static (public_id) + on delete cascade + on update cascade, + credential_purpose text not null + constraint credential_purpose_enm_fkey + references credential_purpose_enm (name) + on delete restrict + on update cascade, + create_time wt_timestamp, + primary key(target_id, credential_static_id, credential_purpose) + ); + comment on table target_static_credential is + 'target_static_credential is a join table between the target, credential_static, and credential_purpose_enm tables. ' + 'A row in the target_static_credential table represents the assignment of a static credential to a target for the specified purpose.'; + + create trigger default_create_time_column before insert on target_static_credential + for each row execute procedure default_create_time(); + + create trigger immutable_columns before update on target_static_credential + for each row execute procedure immutable_columns('target_id', 'credential_static_id', 'credential_purpose', 'create_time'); + + -- replaces view from 10/05_target.up.sql + drop view target_library; + -- target_credential_source provides the store id along with the other data stored in + -- target_credential_library and target_static_credential + create view target_credential_source + as + select + tcl.target_id, + tcl.credential_library_id as credential_source_id, + tcl.credential_purpose, + cl.store_id + from + target_credential_library tcl, + credential_library cl + where + cl.public_id = tcl.credential_library_id + union + select + tcs.target_id, + tcs.credential_static_id as credential_source_id, + tcs.credential_purpose, + cst.store_id + from + target_static_credential tcs, + credential_static cst + where + cst.public_id = tcs.credential_static_id; + comment on view target_credential_source is + 'target_credential_source is a view where each row contains a credential source and the id of the parent credential store. ' + 'No encrypted data is returned. This view can be used to retrieve data which will be returned external to boundary.'; + + create view credential_source_all_types + as + select + public_id, + 'library' as type + from + credential_library + union + select + public_id, + 'static' as type + from + credential_static; + comment on view credential_source_all_types is + 'credential_source_all_types is a view where each row contains the credential source id and type.'; + +commit; diff --git a/internal/proto/controller/storage/target/store/v1/target.proto b/internal/proto/controller/storage/target/store/v1/target.proto index a005eff7c..9b5c38195 100644 --- a/internal/proto/controller/storage/target/store/v1/target.proto +++ b/internal/proto/controller/storage/target/store/v1/target.proto @@ -90,3 +90,49 @@ message CredentialLibrary { // @inject_tag: `gorm:"default:current_timestamp"` timestamp.v1.Timestamp create_time = 40; } + +message StaticCredential { + // target_id of the Target + // @inject_tag: gorm:"primary_key" + string target_id = 10; + + // credential_id of the static Credential + // @inject_tag: `gorm:"column:credential_static_id;primary_key" + string credential_id = 20; + + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + string credential_purpose = 30; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 40; +} + +message CredentialSource { + // target_id of the Target + // @inject_tag: gorm:"primary_key" + string target_id = 10; + + // credential_source_id of the Credential Library or static Credential + // @inject_tag: gorm:"primary_key" + string credential_source_id = 20; + + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + string credential_purpose = 30; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 40; +} + +message CredentialSourceView { + // public_id of the Credential source + // @inject_tag: gorm:"primary_key" + string public_id = 10; + + // type of credential source (library or static) + // @inject_tag: `gorm:"not_null"` + string type = 20; +} diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index 45309cd20..9ecf7a6ca 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -1442,12 +1442,11 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, wrapper wrapping.Wra stores := vault.TestCredentialStores(t, conn, wrapper, params.ScopeId, 1) libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) - libs := []*target.CredentialLibrary{ - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), cred.ApplicationPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), cred.ApplicationPurpose), - } - _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs) + ids := target.CredentialSources{ + ApplicationCredentialIds: []string{libIds[0].GetPublicId(), libIds[1].GetPublicId()}, + } + _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), ids) require.NoError(err) creds := []*DynamicCredential{ NewDynamicCredential(libIds[0].GetPublicId(), cred.ApplicationPurpose), diff --git a/internal/target/credential.go b/internal/target/credential.go new file mode 100644 index 000000000..078e8aeae --- /dev/null +++ b/internal/target/credential.go @@ -0,0 +1,68 @@ +package target + +import ( + "fmt" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target/store" + "google.golang.org/protobuf/proto" +) + +// A StaticCredential is a CredentialSource that represents the relationship +// between a target and a static credential. +type StaticCredential struct { + *store.StaticCredential + tableName string `gorm:"-"` +} + +// NewStaticCredential creates a new in memory StaticCredential +// representing the relationship between targetId and credentialId. +func NewStaticCredential(targetId, credentialId string, purpose credential.Purpose) (*StaticCredential, error) { + const op = "target.StaticCredential" + if targetId == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no target id") + } + if credentialId == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no credential id") + } + + t := &StaticCredential{ + StaticCredential: &store.StaticCredential{ + TargetId: targetId, + CredentialId: credentialId, + CredentialPurpose: string(purpose), + }, + } + return t, nil +} + +func (t *StaticCredential) clone() *StaticCredential { + cp := proto.Clone(t.StaticCredential) + return &StaticCredential{ + StaticCredential: cp.(*store.StaticCredential), + } +} + +// TableName returns the table name. +func (t *StaticCredential) TableName() string { + if t.tableName != "" { + return t.tableName + } + return "target_static_credential" +} + +// SetTableName sets the table name. +func (t *StaticCredential) SetTableName(n string) { + t.tableName = n +} + +func (t *StaticCredential) oplog(op oplog.OpType) oplog.Metadata { + metadata := oplog.Metadata{ + "resource-public-id": []string{fmt.Sprintf("%s:%s:%s", t.TargetId, t.CredentialId, t.CredentialPurpose)}, + "resource-type": []string{"target-credential-static"}, + "op-type": []string{op.String()}, + } + return metadata +} diff --git a/internal/target/credential_library.go b/internal/target/credential_library.go index 8c013a4d8..102ec7963 100644 --- a/internal/target/credential_library.go +++ b/internal/target/credential_library.go @@ -10,8 +10,6 @@ import ( "google.golang.org/protobuf/proto" ) -var _ CredentialSource = (*TargetLibrary)(nil) - // A CredentialLibrary is a CredentialSource that represents the relationship // between a target and a credential library. type CredentialLibrary struct { @@ -74,38 +72,3 @@ func (t *CredentialLibrary) oplog(op oplog.OpType) oplog.Metadata { } return metadata } - -// A TargetLibrary represents the relationship between a target and a -// credential library and includes the id of the credential store that the -// library is a part of and the library's name and description. -// -// It implements the target.CredentialSource interface. -type TargetLibrary struct { - *store.CredentialLibrary - StoreId string -} - -// TableName returns the tablename to override the default gorm table name -func (ts *TargetLibrary) TableName() string { - return "target_library" -} - -// Id returns the ID of the library -func (ts *TargetLibrary) Id() string { - return ts.CredentialLibraryId -} - -// CredentialStoreId returns the ID of the store containing the library -func (ts *TargetLibrary) CredentialStoreId() string { - return ts.StoreId -} - -// CredentialPurpose returns the purpose of the credential -func (ts *TargetLibrary) CredentialPurpose() credential.Purpose { - return credential.Purpose(ts.GetCredentialPurpose()) -} - -// TargetId returns the target linked to this credential source -func (ts *TargetLibrary) TargetId() string { - return ts.GetTargetId() -} diff --git a/internal/target/credential_source.go b/internal/target/credential_source.go index 06e95bb73..88572a3e6 100644 --- a/internal/target/credential_source.go +++ b/internal/target/credential_source.go @@ -1,6 +1,11 @@ package target -import "github.com/hashicorp/boundary/internal/credential" +import ( + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/target/store" +) + +var _ CredentialSource = (*TargetCredentialSource)(nil) // CredentialSource is an interface that can be implemented by both a library // and a singular credential. @@ -10,3 +15,57 @@ type CredentialSource interface { CredentialPurpose() credential.Purpose TargetId() string } + +// CredentialSources contains slices of credential publicIds +// per purpose to be attacehd to the target. +type CredentialSources struct { + ApplicationCredentialIds []string + EgressCredentialIds []string +} + +// A TargetCredentialSource represents the relationship between a target and a +// credential library and includes the id of the credential store that the +// library is a part of and the library's name and description. +// +// It implements the target.CredentialSource interface. +type TargetCredentialSource struct { + *store.CredentialSource + StoreId string +} + +// TableName returns the tablename to override the default gorm table name +func (ts *TargetCredentialSource) TableName() string { + return "target_credential_source" +} + +// Id returns the ID of the credential source +func (ts *TargetCredentialSource) Id() string { + return ts.CredentialSourceId +} + +// CredentialStoreId returns the ID of the store containing the credential source +func (ts *TargetCredentialSource) CredentialStoreId() string { + return ts.StoreId +} + +// CredentialPurpose returns the purpose of the credential +func (ts *TargetCredentialSource) CredentialPurpose() credential.Purpose { + return credential.Purpose(ts.GetCredentialPurpose()) +} + +// TargetId returns the target linked to this credential source +func (ts *TargetCredentialSource) TargetId() string { + return ts.GetTargetId() +} + +// credentialSourceView provides a common way to return credential sources regardless of their +// underlying type (library or static). +type credentialSourceView struct { + *store.CredentialSourceView + tableName string `gorm:"-"` +} + +// TableName returns the tablename to override the default gorm table name +func (csv *credentialSourceView) TableName() string { + return "credential_source_all_types" +} diff --git a/internal/target/credential_test.go b/internal/target/credential_test.go new file mode 100644 index 000000000..7915f87a9 --- /dev/null +++ b/internal/target/credential_test.go @@ -0,0 +1,69 @@ +package target_test + +import ( + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStaticCredential_New(t *testing.T) { + type args struct { + targetId string + credId string + } + tests := []struct { + name string + args args + want *target.StaticCredential + wantErr errors.Code + }{ + { + name: "no-targetId", + args: args{ + credId: "cred_0000000", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "no-credId", + args: args{ + targetId: "targ_0000000", + }, + wantErr: errors.InvalidParameter, + }, + { + name: "valid", + args: args{ + targetId: "targ_0000000", + credId: "cred_0000000", + }, + want: &target.StaticCredential{ + StaticCredential: &store.StaticCredential{ + TargetId: "targ_0000000", + CredentialId: "cred_0000000", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + got, err := target.NewStaticCredential(tt.args.targetId, tt.args.credId, credential.ApplicationPurpose) + if tt.wantErr != 0 { + assert.Truef(errors.Match(errors.T(tt.wantErr), err), "want err: %q got: %q", tt.wantErr, err) + assert.Nil(got) + return + } + require.NoError(err) + require.NotNil(got) + assert.EqualValues(tt.want, got) + }) + } +} diff --git a/internal/target/options.go b/internal/target/options.go index e3d0421ac..593694df0 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -31,6 +31,7 @@ type options struct { WithType subtypes.Subtype WithHostSources []string WithCredentialLibraries []*CredentialLibrary + WithStaticCredentials []*StaticCredential WithSessionMaxSeconds uint32 WithSessionConnectionLimit int32 WithPublicId string @@ -51,6 +52,7 @@ func getDefaultOptions() options { WithType: "", WithHostSources: nil, WithCredentialLibraries: nil, + WithStaticCredentials: nil, WithSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), WithSessionConnectionLimit: 1, WithPublicId: "", @@ -137,6 +139,13 @@ func WithCredentialLibraries(cl []*CredentialLibrary) Option { } } +// WithStaticCredentials provides an option for providing a list of static credentials. +func WithStaticCredentials(c []*StaticCredential) Option { + return func(o *options) { + o.WithStaticCredentials = c + } +} + func WithSessionMaxSeconds(dur uint32) Option { return func(o *options) { o.WithSessionMaxSeconds = dur diff --git a/internal/target/options_test.go b/internal/target/options_test.go index 0746102d6..fad53f0df 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -141,4 +141,37 @@ func Test_GetOpts(t *testing.T) { } assert.Equal(opts, testOpts) }) + t.Run("WithStaticCredentials", func(t *testing.T) { + assert := assert.New(t) + opts := GetOpts(WithStaticCredentials([]*StaticCredential{ + { + StaticCredential: &store.StaticCredential{ + CredentialId: "alice", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + { + StaticCredential: &store.StaticCredential{ + CredentialId: "bob", + CredentialPurpose: string(credential.EgressPurpose), + }, + }, + })) + testOpts := getDefaultOptions() + testOpts.WithStaticCredentials = []*StaticCredential{ + { + StaticCredential: &store.StaticCredential{ + CredentialId: "alice", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + { + StaticCredential: &store.StaticCredential{ + CredentialId: "bob", + CredentialPurpose: string(credential.EgressPurpose), + }, + }, + } + assert.Equal(opts, testOpts) + }) } diff --git a/internal/target/query.go b/internal/target/query.go index 4fd31c14c..198df571a 100644 --- a/internal/target/query.go +++ b/internal/target/query.go @@ -3,46 +3,51 @@ package target const ( setChangesQuery = ` with -set_libraries (library_id) as ( +set_sources (source_id, type) as ( -- returns the SET list - select public_id - from credential_library + select public_id, type + from credential_source_all_types where public_id in (%s) ), -current_libraries (library_id) as ( +current_sources (source_id, type) as ( -- returns the current list - select credential_library_id + select credential_library_id, 'library' from target_credential_library where target_id = @target_id and credential_purpose = @purpose + union + select credential_static_id, 'static' + from target_static_credential + where target_id = @target_id + and credential_purpose = @purpose ), -keep_libraries (library_id) as ( +keep_sources (source_id) as ( -- returns the KEEP list - select library_id - from current_libraries - where library_id in (select * from set_libraries) + select source_id + from current_sources + where source_id in (select source_id from set_sources) ), -delete_libraries (library_id) as ( +delete_sources (source_id, type) as ( -- returns the DELETE list - select library_id - from current_libraries - where library_id not in (select * from set_libraries) + select source_id, type + from current_sources + where source_id not in (select source_id from set_sources) ), -insert_libraries (library_id) as ( +insert_sources (source_id, type) as ( -- returns the ADD list - select library_id - from set_libraries - where library_id not in (select * from keep_libraries) + select source_id, type + from set_sources + where source_id not in (select * from keep_sources) ), -final (action, library_id) as ( - select 'delete', library_id - from delete_libraries +final (action, source_id, type) as ( + select 'delete', source_id, type + from delete_sources union - select 'add', library_id - from insert_libraries + select 'add', source_id, type + from insert_sources ) select * from final -order by action, library_id; +order by action, source_id; ` targetPublicIdList = ` diff --git a/internal/target/registry.go b/internal/target/registry.go index e54abb82f..060e27c90 100644 --- a/internal/target/registry.go +++ b/internal/target/registry.go @@ -25,9 +25,9 @@ type VetFunc func(context.Context, Target) error // paths are valid and be used to update a target in the Repository. type VetForUpdateFunc func(context.Context, Target, []string) error -// VetCredentialLibrariesFunc is a function that checks the given CredentialLibraries -// to ensure that they are valid for a Target subtype. -type VetCredentialLibrariesFunc func(context.Context, []*CredentialLibrary) error +// VetCredentialSourcesFunc is a function that checks the given CredentialLibraries +// and StaticCredentials to ensure that they are valid for a Target subtype. +type VetCredentialSourcesFunc func(context.Context, []*CredentialLibrary, []*StaticCredential) error // targetHooks defines the interface containing all the hooks needed for // managing target suptypes. @@ -43,9 +43,9 @@ type targetHooks interface { // and values for updating the database for this type of target given the // field mask paths. VetForUpdate(ctx context.Context, t Target, paths []string) error - // VetCredentialLibraries checks that the provided credential - // libriaries are properly built for association with a target of this type. - VetCredentialLibraries(ctx context.Context, cls []*CredentialLibrary) error + // VetCredentialSources checks that the provided credential libriaries and + // static credentials are properly built for association with a target of this type. + VetCredentialSources(ctx context.Context, cls []*CredentialLibrary, creds []*StaticCredential) error } type registryEntry struct { @@ -118,12 +118,13 @@ func (r *registry) vetForUpdateFunc(s subtypes.Subtype) (VetForUpdateFunc, bool) return entry.targetHooks.VetForUpdate, ok } -func (r *registry) vetCredentialLibrariesFunc(s subtypes.Subtype) (VetCredentialLibrariesFunc, bool) { +func (r *registry) vetCredentialSourcesFunc(s subtypes.Subtype) (VetCredentialSourcesFunc, bool) { entry, ok := r.get(s) if !ok { return nil, ok } - return entry.targetHooks.VetCredentialLibraries, ok + + return entry.targetHooks.VetCredentialSources, ok } func (r *registry) idPrefix(s subtypes.Subtype) (string, bool) { diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index 486e4f278..a32c655b1 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -11,41 +11,15 @@ import ( "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" - "github.com/hashicorp/boundary/internal/target/store" + "github.com/hashicorp/boundary/internal/types/subtypes" + "github.com/hashicorp/go-secure-stdlib/strutil" ) -// credentialLibrariesPurposeMap is used to group CredentialLibraries by purpose. -type credentialLibrariesPurposeMap map[string][]*CredentialLibrary - -func (c credentialLibrariesPurposeMap) add(cl *CredentialLibrary) { - var s []*CredentialLibrary - var ok bool - - s, ok = c[cl.CredentialPurpose] - if !ok { - s = make([]*CredentialLibrary, 0, 1) - } - - s = append(s, cl) - c[cl.CredentialPurpose] = s -} - -func newCredentialLibrariesPurposeMap(cls []*CredentialLibrary) credentialLibrariesPurposeMap { - clpm := make(credentialLibrariesPurposeMap) - for _, p := range credential.ValidPurposes { - clpm[p.String()] = make([]*CredentialLibrary, 0) - } - for _, cl := range cls { - clpm.add(cl) - } - return clpm -} - -// AddTargetCredentialSources adds the cIds to the targetId in the repository. The target -// and the list of credential sources attached to the target, after cIds are added, +// AddTargetCredentialSources adds the credential source ids by purpose to the targetId in the repository. +// The target and the list of credential sources attached to the target, after ids are added, // will be returned on success. // The targetVersion must match the current version of the targetId in the repository. -func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...Option) (Target, []HostSource, []CredentialSource, error) { +func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, idsByPurpose CredentialSources, _ ...Option) (Target, []HostSource, []CredentialSource, error) { const op = "target.(Repository).AddTargetCredentialSources" if targetId == "" { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing target id") @@ -53,14 +27,6 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st if targetVersion == 0 { return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing version") } - if len(cls) == 0 { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential libraries") - } - - addCredLibs := make([]interface{}, 0, len(cls)) - for _, cl := range cls { - addCredLibs = append(addCredLibs, cl) - } t := allocTargetView() t.PublicId = targetId @@ -74,16 +40,15 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } - vetCredentialLibraries, ok := subtypeRegistry.vetCredentialLibrariesFunc(t.Subtype()) - if !ok { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) - } - if err := vetCredentialLibraries(ctx, cls); err != nil { - return nil, nil, nil, err + addCredLibs, addStaticCreds, err := r.createSources(ctx, targetId, t.Subtype(), idsByPurpose) + if err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op) } target := alloc() - target.SetPublicId(ctx, t.PublicId) + if err := target.SetPublicId(ctx, t.PublicId); err != nil { + return nil, nil, nil, errors.Wrap(ctx, err, op) + } target.SetVersion(targetVersion + 1) metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) @@ -101,7 +66,8 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st db.StdRetryCnt, db.ExpBackoff{}, func(reader db.Reader, w db.Writer) error { - msgs := make([]*oplog.Message, 0, 2) + numOplogMsgs := 1 + len(addCredLibs) + len(addStaticCreds) + msgs := make([]*oplog.Message, 0, numOplogMsgs) targetTicket, err := w.GetTicket(ctx, target) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to get ticket")) @@ -120,11 +86,29 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st } msgs = append(msgs, &targetOplogMsg) - credLibsOplogMsgs := make([]*oplog.Message, 0, len(addCredLibs)) - if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs)); err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target credential sources")) + if len(addCredLibs) > 0 { + i := make([]interface{}, 0, len(addCredLibs)) + for _, cl := range addCredLibs { + i = append(i, cl) + } + credLibsOplogMsgs := make([]*oplog.Message, 0, len(addCredLibs)) + if err := w.CreateItems(ctx, i, db.NewOplogMsgs(&credLibsOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target credential library")) + } + msgs = append(msgs, credLibsOplogMsgs...) + } + + if len(addStaticCreds) > 0 { + i := make([]interface{}, 0, len(addStaticCreds)) + for _, c := range addStaticCreds { + i = append(i, c) + } + credStaticOplogMsgs := make([]*oplog.Message, 0, len(addStaticCreds)) + if err := w.CreateItems(ctx, i, db.NewOplogMsgs(&credStaticOplogMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to create target static credential")) + } + msgs = append(msgs, credStaticOplogMsgs...) } - msgs = append(msgs, credLibsOplogMsgs...) if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) @@ -148,7 +132,7 @@ func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId st // DeleteTargetCredentialSources deletes credential sources from a target in the repository. // The target's current db version must match the targetVersion or an error will be returned. -func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...Option) (int, error) { +func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, idsByPurpose CredentialSources, _ ...Option) (int, error) { const op = "target.(Repository).DeleteTargetCredentialSources" if targetId == "" { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target id") @@ -156,14 +140,6 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId if targetVersion == 0 { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") } - if len(cls) == 0 { - return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential source ids") - } - - deleteCredLibs := make([]interface{}, 0, len(cls)) - for _, cl := range cls { - deleteCredLibs = append(deleteCredLibs, cl) - } t := allocTargetView() t.PublicId = targetId @@ -172,12 +148,19 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId } var metadata oplog.Metadata + deleteCredLibs, deleteStaticCred, err := r.createSources(ctx, targetId, t.Subtype(), idsByPurpose) + if err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + alloc, ok := subtypeRegistry.allocFunc(t.Subtype()) if !ok { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } target := alloc() - target.SetPublicId(ctx, t.PublicId) + if err := target.SetPublicId(ctx, t.PublicId); err != nil { + return db.NoRowsAffected, errors.Wrap(ctx, err, op) + } target.SetVersion(targetVersion + 1) metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) @@ -212,15 +195,41 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId } msgs = append(msgs, &targetOplogMsg) - credLibsOplogMsgs := make([]*oplog.Message, 0, len(deleteCredLibs)) - rowsDeleted, err = w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&credLibsOplogMsgs)) - if err != nil { - return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target credential sources")) + if len(deleteCredLibs) > 0 { + i := make([]interface{}, 0, len(deleteCredLibs)) + for _, cl := range deleteCredLibs { + i = append(i, cl) + } + + credLibsOplogMsgs := make([]*oplog.Message, 0, len(deleteCredLibs)) + cnt, err := w.DeleteItems(ctx, i, db.NewOplogMsgs(&credLibsOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target credential libraries")) + } + if cnt != len(deleteCredLibs) { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("credential libraries deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + } + rowsDeleted += cnt + msgs = append(msgs, credLibsOplogMsgs...) } - if rowsDeleted != len(deleteCredLibs) { - return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("credential sources deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + + if len(deleteStaticCred) > 0 { + i := make([]interface{}, 0, len(deleteStaticCred)) + for _, cl := range deleteStaticCred { + i = append(i, cl) + } + + staticCredOplogMsgs := make([]*oplog.Message, 0, len(deleteStaticCred)) + cnt, err := w.DeleteItems(ctx, i, db.NewOplogMsgs(&staticCredOplogMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target static credential")) + } + if cnt != len(deleteStaticCred) { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("static credential deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + } + rowsDeleted += cnt + msgs = append(msgs, staticCredOplogMsgs...) } - msgs = append(msgs, credLibsOplogMsgs...) if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) @@ -237,7 +246,7 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId // SetTargetCredentialSources will set the target's credential sources. Set will add // and/or delete credential sources as need to reconcile the existing credential sources // with the request. If clIds is empty, all the credential sources will be cleared from the target. -func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...Option) ([]HostSource, []CredentialSource, int, error) { +func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, ids CredentialSources, _ ...Option) ([]HostSource, []CredentialSource, int, error) { const op = "target.(Repository).SetTargetCredentialSources" if targetId == "" { return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target id") @@ -246,19 +255,29 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") } - clpm := newCredentialLibrariesPurposeMap(cls) + var ( + addCredLibs []*CredentialLibrary + delCredLibs []*CredentialLibrary + addStaticCred []*StaticCredential + delStaticCred []*StaticCredential + ) - var changes []*change - - for purpose, cls := range clpm { - c, err := r.changes(ctx, targetId, cls, purpose) + byPurpose := map[credential.Purpose][]string{ + credential.ApplicationPurpose: ids.ApplicationCredentialIds, + credential.EgressPurpose: ids.EgressCredentialIds, + } + for p, ids := range byPurpose { + addL, delL, addS, delS, err := r.changes(ctx, targetId, ids, p) if err != nil { return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) } - changes = append(changes, c...) + addCredLibs = append(addCredLibs, addL...) + delCredLibs = append(delCredLibs, delL...) + addStaticCred = append(addStaticCred, addS...) + delStaticCred = append(delStaticCred, delS...) } - if len(changes) == 0 { + if len(addCredLibs)+len(delCredLibs)+len(addStaticCred)+len(delStaticCred) == 0 { // Nothing needs to be changed, return early hostSets, err := fetchHostSources(ctx, r.reader, targetId) if err != nil { @@ -271,16 +290,6 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return hostSets, credSources, db.NoRowsAffected, nil } - var deleteCredLibs, addCredLibs []interface{} - for _, c := range changes { - switch c.action { - case "delete": - deleteCredLibs = append(deleteCredLibs, c.credentialLibrary) - case "add": - addCredLibs = append(addCredLibs, c.credentialLibrary) - } - } - t := allocTargetView() t.PublicId = targetId if err := r.reader.LookupByPublicId(ctx, &t); err != nil { @@ -293,16 +302,19 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } - vetCredentialLibraries, ok := subtypeRegistry.vetCredentialLibrariesFunc(t.Subtype()) + vetCredentialSources, ok := subtypeRegistry.vetCredentialSourcesFunc(t.Subtype()) if !ok { return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("%s is an unsupported target type %s", t.PublicId, t.Type)) } - if err := vetCredentialLibraries(ctx, cls); err != nil { + // Validate add sources on target + if err := vetCredentialSources(ctx, addCredLibs, addStaticCred); err != nil { return nil, nil, db.NoRowsAffected, err } target := alloc() - target.SetPublicId(ctx, t.PublicId) + if err := target.SetPublicId(ctx, t.PublicId); err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } target.SetVersion(targetVersion + 1) metadata = target.Oplog(oplog.OpType_OP_TYPE_UPDATE) @@ -340,8 +352,12 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st // add new credential libraries if len(addCredLibs) > 0 { + i := make([]interface{}, 0, len(addCredLibs)) + for _, cl := range addCredLibs { + i = append(i, cl) + } addMsgs := make([]*oplog.Message, 0, len(addCredLibs)) - if err := w.CreateItems(ctx, addCredLibs, db.NewOplogMsgs(&addMsgs)); err != nil { + if err := w.CreateItems(ctx, i, db.NewOplogMsgs(&addMsgs)); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add target credential libraries")) } rowsAffected += len(addMsgs) @@ -350,19 +366,58 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st } // delete existing credential libraries not part of set - if len(deleteCredLibs) > 0 { - delMsgs := make([]*oplog.Message, 0, len(deleteCredLibs)) - rowsDeleted, err := w.DeleteItems(ctx, deleteCredLibs, db.NewOplogMsgs(&delMsgs)) + if len(delCredLibs) > 0 { + i := make([]interface{}, 0, len(delCredLibs)) + for _, cl := range delCredLibs { + i = append(i, cl) + } + delMsgs := make([]*oplog.Message, 0, len(delCredLibs)) + rowsDeleted, err := w.DeleteItems(ctx, i, db.NewOplogMsgs(&delMsgs)) if err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target credential libraries")) } if rowsDeleted != len(delMsgs) { - return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("target credential libraries deleted %d did not match request for %d", rowsDeleted, len(deleteCredLibs))) + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("target credential libraries deleted %d did not match request for %d", rowsDeleted, len(delCredLibs))) } rowsAffected += rowsDeleted msgs = append(msgs, delMsgs...) metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) } + + // add new static credential + if len(addStaticCred) > 0 { + i := make([]interface{}, 0, len(addStaticCred)) + for _, cl := range addStaticCred { + i = append(i, cl) + } + addMsgs := make([]*oplog.Message, 0, len(addStaticCred)) + if err := w.CreateItems(ctx, i, db.NewOplogMsgs(&addMsgs)); err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to add target static credential ")) + } + rowsAffected += len(addMsgs) + msgs = append(msgs, addMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_CREATE.String()) + } + + // delete existing static credentials not part of set + if len(delStaticCred) > 0 { + i := make([]interface{}, 0, len(delStaticCred)) + for _, cl := range delStaticCred { + i = append(i, cl) + } + delMsgs := make([]*oplog.Message, 0, len(delStaticCred)) + rowsDeleted, err := w.DeleteItems(ctx, i, db.NewOplogMsgs(&delMsgs)) + if err != nil { + return errors.Wrap(ctx, err, op, errors.WithMsg("unable to delete target static credential")) + } + if rowsDeleted != len(delMsgs) { + return errors.New(ctx, errors.MultipleRecords, op, fmt.Sprintf("target static credential deleted %d did not match request for %d", rowsDeleted, len(delStaticCred))) + } + rowsAffected += rowsDeleted + msgs = append(msgs, delMsgs...) + metadata["op-type"] = append(metadata["op-type"], oplog.OpType_OP_TYPE_DELETE.String()) + } + if err := w.WriteOplogEntryWith(ctx, oplogWrapper, targetTicket, metadata, msgs); err != nil { return errors.Wrap(ctx, err, op, errors.WithMsg("unable to write oplog")) } @@ -384,17 +439,17 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return hostSources, credSources, rowsAffected, nil } -type change struct { - action string - credentialLibrary *CredentialLibrary -} - type changeQueryResult struct { - Action string - LibraryId string + Action string + Type string + SourceId string } -func (r *Repository) changes(ctx context.Context, targetId string, cls []*CredentialLibrary, purpose string) ([]*change, error) { +func (r *Repository) changes(ctx context.Context, targetId string, ids []string, purpose credential.Purpose) ( + addCredLib, delCredLib []*CredentialLibrary, + addStaticCred, delStaticCred []*StaticCredential, + err error, +) { const op = "target.(Repository).changes" // TODO ensure that all cls have the same purpose as the given purpose? @@ -402,8 +457,8 @@ func (r *Repository) changes(ctx context.Context, targetId string, cls []*Creden var inClauseSpots []string var params []interface{} params = append(params, sql.Named("target_id", targetId), sql.Named("purpose", purpose)) - for idx, cl := range cls { - params = append(params, sql.Named(fmt.Sprintf("%d", idx+1), cl.CredentialLibraryId)) + for idx, id := range ids { + params = append(params, sql.Named(fmt.Sprintf("%d", idx+1), id)) inClauseSpots = append(inClauseSpots, fmt.Sprintf("@%d", idx+1)) } inClause := strings.Join(inClauseSpots, ",") @@ -414,45 +469,121 @@ func (r *Repository) changes(ctx context.Context, targetId string, cls []*Creden query := fmt.Sprintf(setChangesQuery, inClause) rows, err := r.reader.Query(ctx, query, params) if err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("query failed")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("query failed")) } defer rows.Close() - var changes []*change for rows.Next() { var chg changeQueryResult if err := r.reader.ScanRows(ctx, rows, &chg); err != nil { - return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed")) + return nil, nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed")) + } + switch chg.Type { + case "library": + lib, err := NewCredentialLibrary(targetId, chg.SourceId, purpose) + if err != nil { + return nil, nil, nil, nil, errors.Wrap(ctx, err, op) + } + switch chg.Action { + case "delete": + delCredLib = append(delCredLib, lib) + default: + addCredLib = append(addCredLib, lib) + } + case "static": + cred, err := NewStaticCredential(targetId, chg.SourceId, purpose) + if err != nil { + return nil, nil, nil, nil, errors.Wrap(ctx, err, op) + } + switch chg.Action { + case "delete": + delStaticCred = append(delStaticCred, cred) + default: + addStaticCred = append(addStaticCred, cred) + } } - changes = append(changes, &change{ - action: chg.Action, - credentialLibrary: &CredentialLibrary{ - CredentialLibrary: &store.CredentialLibrary{ - TargetId: targetId, - CredentialLibraryId: chg.LibraryId, - CredentialPurpose: purpose, - }, - }, - }) } - return changes, nil + return addCredLib, delCredLib, addStaticCred, delStaticCred, nil } func fetchCredentialSources(ctx context.Context, r db.Reader, targetId string) ([]CredentialSource, error) { const op = "target.fetchCredentialSources" - var libraries []*TargetLibrary - if err := r.SearchWhere(ctx, &libraries, "target_id = ?", []interface{}{targetId}); err != nil { + var sources []*TargetCredentialSource + if err := r.SearchWhere(ctx, &sources, "target_id = ?", []interface{}{targetId}); err != nil { return nil, errors.Wrap(ctx, err, op) } - // FIXME: When we have static creds, there will need to be an updated view - // that unions between libs and creds, at which point the type above will - // change. For now we just take the libraries and wrap them. - if len(libraries) == 0 { + if len(sources) == 0 { return nil, nil } - ret := make([]CredentialSource, len(libraries)) - for i, lib := range libraries { - ret[i] = lib + ret := make([]CredentialSource, len(sources)) + for i, source := range sources { + ret[i] = source } return ret, nil } + +func (r *Repository) createSources(ctx context.Context, tId string, tSubtype subtypes.Subtype, credSources CredentialSources) ([]*CredentialLibrary, []*StaticCredential, error) { + const op = "target.(Repository).createSources" + + // Get a list of unique ids being attached to the target, to be used for looking up the source type (library or static) + ids := make([]string, 0, len(credSources.ApplicationCredentialIds)+len(credSources.EgressCredentialIds)) + ids = append(ids, credSources.ApplicationCredentialIds...) + ids = append(ids, credSources.EgressCredentialIds...) + totalCreds := len(ids) + ids = strutil.RemoveDuplicates(ids, false) + if len(ids) == 0 { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential sources") + } + + // Fetch credentials from database to determine the type of credential + var credView []*credentialSourceView + if err := r.reader.SearchWhere(ctx, &credView, "public_id in (?)", []interface{}{ids}); err != nil { + return nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("can't retrieve credentials")) + } + if len(ids) != len(credView) { + return nil, nil, errors.New(ctx, errors.NotSpecificIntegrity, op, + fmt.Sprintf("mismatch between request and returned source ids, expected %d got %d", len(ids), len(credView))) + } + + // Create a map between credential source ID and it's type (library or static). + // This will allow for a quick lookup when calling the corresponding New below + credTypeById := make(map[string]string, len(ids)) + for _, cv := range credView { + credTypeById[cv.GetPublicId()] = cv.GetType() + } + + credLibs := make([]*CredentialLibrary, 0, totalCreds) + staticCred := make([]*StaticCredential, 0, totalCreds) + byPurpose := map[credential.Purpose][]string{ + credential.ApplicationPurpose: credSources.ApplicationCredentialIds, + credential.EgressPurpose: credSources.EgressCredentialIds, + } + for purpose, ids := range byPurpose { + for _, id := range ids { + switch credTypeById[id] { + case "library": + lib, err := NewCredentialLibrary(tId, id, purpose) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + credLibs = append(credLibs, lib) + case "static": + cred, err := NewStaticCredential(tId, id, purpose) + if err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + staticCred = append(staticCred, cred) + } + } + } + + vetCredentialSources, ok := subtypeRegistry.vetCredentialSourcesFunc(tSubtype) + if !ok { + return nil, nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("is an unsupported target type %s", tSubtype)) + } + if err := vetCredentialSources(ctx, credLibs, staticCred); err != nil { + return nil, nil, errors.Wrap(ctx, err, op) + } + + return credLibs, staticCred, nil +} diff --git a/internal/target/repository_credential_source_test.go b/internal/target/repository_credential_source_test.go index 96f262ad9..e399ffead 100644 --- a/internal/target/repository_credential_source_test.go +++ b/internal/target/repository_credential_source_test.go @@ -5,12 +5,14 @@ import ( "testing" "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" "github.com/hashicorp/boundary/internal/target/targettest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,8 +36,8 @@ func (h hooks) VetForUpdate(ctx context.Context, t target.Target, paths []string return targettest.VetForUpdate(ctx, t, paths) } -func (h hooks) VetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error { - return targettest.VetCredentialLibraries(ctx, cls) +func (h hooks) VetCredentialSources(ctx context.Context, cls []*target.CredentialLibrary, creds []*target.StaticCredential) error { + return targettest.VetCredentialSources(ctx, cls, creds) } func TestRepository_SetTargetCredentialSources(t *testing.T) { @@ -52,31 +54,40 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, proj := iam.TestScopes(t, iamRepo) - cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] - credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) + storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) lib1 := credLibs[0] lib2 := credLibs[1] - setupFn := func(tar target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) { - credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 10) - cls := make([]*target.CredentialLibrary, 0, len(credLibs)) + storeStatic := static.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credsStatic := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", storeStatic.GetPublicId(), proj.GetPublicId(), 2) + cred1 := credsStatic[0] + cred2 := credsStatic[1] + + setupFn := func(tar target.Target) ([]target.CredentialSource, target.CredentialSources) { + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 5) + var ids target.CredentialSources for _, cl := range credLibs { - cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, cl.GetPublicId()) + } + creds := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", storeStatic.GetPublicId(), proj.GetPublicId(), 5) + for _, cred := range creds { + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, cred.GetPublicId()) } - _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) + _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, ids) require.NoError(t, err) require.Equal(t, 10, len(created)) - return created, cls + return created, ids } type args struct { - targetVersion uint32 - cls []*target.CredentialLibrary - addToOrigLibs bool + targetVersion uint32 + ids target.CredentialSources + addToOrigSources bool } tests := []struct { name string - setup func(target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) + setup func(target.Target) ([]target.CredentialSource, target.CredentialSources) args args wantAffectedRows int wantErr bool @@ -87,7 +98,6 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{}, }, wantErr: false, wantAffectedRows: 10, @@ -96,62 +106,47 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { name: "no-change", setup: setupFn, args: args{ - targetVersion: 2, - cls: []*target.CredentialLibrary{}, - addToOrigLibs: true, + targetVersion: 2, + addToOrigSources: true, }, wantErr: false, wantAffectedRows: 0, }, { - name: "add-cred-sources", + name: "add-cred-library", setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: false, wantAffectedRows: 2, }, { - name: "add-multiple-purposes", + name: "add-cred-static", setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: false, - wantAffectedRows: 3, + wantAffectedRows: 2, }, { - name: "remove-add-change-purpose", - setup: func(tar target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) { - cls := []*target.CredentialLibrary{ - target.TestNewCredentialLibrary(tar.GetPublicId(), lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), lib2.PublicId, credential.ApplicationPurpose), - } - - _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) - require.NoError(t, err) - require.Equal(t, 2, len(created)) - return created, cls - }, + name: "add-cred-lib-and-static", + setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.EgressPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, lib1.PublicId, cred2.PublicId, lib2.PublicId}, }, - addToOrigLibs: false, + addToOrigSources: true, }, wantErr: false, wantAffectedRows: 4, @@ -161,11 +156,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 0, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: true, wantErrCode: errors.InvalidParameter, @@ -175,11 +169,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 1000, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: true, wantErrCode: errors.VersionMismatch, @@ -189,15 +182,52 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: false, + addToOrigSources: false, }, wantErr: false, wantAffectedRows: 12, }, + { + name: "remove existing and add cred static", + setup: setupFn, + args: args{ + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId}, + }, + addToOrigSources: false, + }, + wantErr: false, + wantAffectedRows: 12, + }, + { + name: "remove existing and add cred static and lib", + setup: setupFn, + args: args{ + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, lib2.PublicId}, + }, + addToOrigSources: false, + }, + wantErr: false, + wantAffectedRows: 12, + }, + { + name: "egress-credential-purpose", + setup: setupFn, + args: args{ + targetVersion: 2, + ids: target.CredentialSources{ + EgressCredentialIds: []string{lib1.PublicId}, + }, + addToOrigSources: true, + }, + wantAffectedRows: 1, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -207,23 +237,28 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { tar := targettest.TestNewTestTarget(ctx, t, conn, proj.PublicId, tt.name) var origCredSources []target.CredentialSource - var origCredLibraries []*target.CredentialLibrary - if tt.setup != nil { - origCredSources, origCredLibraries = tt.setup(tar) - } - wantCredSources := make(map[string]target.CredentialSource) - for _, cl := range tt.args.cls { - cl.TargetId = tar.GetPublicId() - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ - CredentialLibrary: cl.CredentialLibrary, + if tt.setup != nil { + var origCredIds target.CredentialSources + origCredSources, origCredIds = tt.setup(tar) + + if tt.args.addToOrigSources { + tt.args.ids.ApplicationCredentialIds = append(tt.args.ids.ApplicationCredentialIds, origCredIds.ApplicationCredentialIds...) + tt.args.ids.EgressCredentialIds = append(tt.args.ids.EgressCredentialIds, origCredIds.EgressCredentialIds...) } } - if tt.args.addToOrigLibs { - tt.args.cls = append(tt.args.cls, origCredLibraries...) - for _, cl := range origCredLibraries { - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ - CredentialLibrary: cl.CredentialLibrary, + + byPurpose := map[credential.Purpose][]string{ + credential.ApplicationPurpose: tt.args.ids.ApplicationCredentialIds, + credential.EgressPurpose: tt.args.ids.EgressCredentialIds, + } + for purpose, ids := range byPurpose { + for _, id := range ids { + wantCredSources[id+"_"+string(purpose)] = &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: id, + CredentialPurpose: string(purpose), + }, } } } @@ -232,7 +267,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(origCredSources, lookupCredSources) - _, got, affectedRows, err := repo.SetTargetCredentialSources(ctx, tar.GetPublicId(), tt.args.targetVersion, tt.args.cls) + _, gotSources, affectedRows, err := repo.SetTargetCredentialSources(ctx, tar.GetPublicId(), tt.args.targetVersion, tt.args.ids) if tt.wantErr { require.Error(err) assert.Equal(0, affectedRows) @@ -243,9 +278,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(tt.wantAffectedRows, affectedRows) - assert.Equal(len(wantCredSources), len(got)) - - for _, cs := range got { + for _, cs := range gotSources { w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] assert.True(ok, "got unexpected credentialsource %v", cs) assert.Equal(w.Id(), cs.Id()) @@ -263,8 +296,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("missing-target-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cl1 := target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, []*target.CredentialLibrary{cl1}) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, + target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error()) @@ -272,8 +305,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cl1 := target.TestNewCredentialLibrary("fake-target-id", lib1.PublicId, credential.ApplicationPurpose) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, []*target.CredentialLibrary{cl1}) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, + target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) diff --git a/internal/target/store/target.pb.go b/internal/target/store/target.pb.go index a6f784a00..9df9a13d2 100644 --- a/internal/target/store/target.pb.go +++ b/internal/target/store/target.pb.go @@ -331,6 +331,223 @@ func (x *CredentialLibrary) GetCreateTime() *timestamp.Timestamp { return nil } +type StaticCredential struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // target_id of the Target + // @inject_tag: gorm:"primary_key" + TargetId string `protobuf:"bytes,10,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"primary_key"` + // credential_id of the static Credential + // @inject_tag: `gorm:"column:credential_static_id;primary_key" + CredentialId string `protobuf:"bytes,20,opt,name=credential_id,json=credentialId,proto3" json:"credential_id,omitempty" gorm:"column:credential_static_id;primary_key"` + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + CredentialPurpose string `protobuf:"bytes,30,opt,name=credential_purpose,json=credentialPurpose,proto3" json:"credential_purpose,omitempty" gorm:"primary_key"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *StaticCredential) Reset() { + *x = StaticCredential{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StaticCredential) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StaticCredential) ProtoMessage() {} + +func (x *StaticCredential) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StaticCredential.ProtoReflect.Descriptor instead. +func (*StaticCredential) Descriptor() ([]byte, []int) { + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{3} +} + +func (x *StaticCredential) GetTargetId() string { + if x != nil { + return x.TargetId + } + return "" +} + +func (x *StaticCredential) GetCredentialId() string { + if x != nil { + return x.CredentialId + } + return "" +} + +func (x *StaticCredential) GetCredentialPurpose() string { + if x != nil { + return x.CredentialPurpose + } + return "" +} + +func (x *StaticCredential) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +type CredentialSource struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // target_id of the Target + // @inject_tag: gorm:"primary_key" + TargetId string `protobuf:"bytes,10,opt,name=target_id,json=targetId,proto3" json:"target_id,omitempty" gorm:"primary_key"` + // credential_source_id of the Credential Library or static Credential + // @inject_tag: gorm:"primary_key" + CredentialSourceId string `protobuf:"bytes,20,opt,name=credential_source_id,json=credentialSourceId,proto3" json:"credential_source_id,omitempty" gorm:"primary_key"` + // credential_purpose is the purpose of the credential for the target + // @inject_tag: gorm:"primary_key" + CredentialPurpose string `protobuf:"bytes,30,opt,name=credential_purpose,json=credentialPurpose,proto3" json:"credential_purpose,omitempty" gorm:"primary_key"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,40,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` +} + +func (x *CredentialSource) Reset() { + *x = CredentialSource{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialSource) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialSource) ProtoMessage() {} + +func (x *CredentialSource) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CredentialSource.ProtoReflect.Descriptor instead. +func (*CredentialSource) Descriptor() ([]byte, []int) { + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{4} +} + +func (x *CredentialSource) GetTargetId() string { + if x != nil { + return x.TargetId + } + return "" +} + +func (x *CredentialSource) GetCredentialSourceId() string { + if x != nil { + return x.CredentialSourceId + } + return "" +} + +func (x *CredentialSource) GetCredentialPurpose() string { + if x != nil { + return x.CredentialPurpose + } + return "" +} + +func (x *CredentialSource) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +type CredentialSourceView struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id of the Credential source + // @inject_tag: gorm:"primary_key" + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // type of credential source (library or static) + // @inject_tag: `gorm:"not_null"` + Type string `protobuf:"bytes,20,opt,name=type,proto3" json:"type,omitempty" gorm:"not_null"` +} + +func (x *CredentialSourceView) Reset() { + *x = CredentialSourceView{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CredentialSourceView) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CredentialSourceView) ProtoMessage() {} + +func (x *CredentialSourceView) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_store_v1_target_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CredentialSourceView.ProtoReflect.Descriptor instead. +func (*CredentialSourceView) Descriptor() ([]byte, []int) { + return file_controller_storage_target_store_v1_target_proto_rawDescGZIP(), []int{5} +} + +func (x *CredentialSourceView) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *CredentialSourceView) GetType() string { + if x != nil { + return x.Type + } + return "" +} + var File_controller_storage_target_store_v1_target_proto protoreflect.FileDescriptor var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ @@ -397,12 +614,43 @@ var file_controller_storage_target_store_v1_target_proto_rawDesc = []byte{ 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x42, 0x3b, 0x5a, 0x39, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, - 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, - 0x6f, 0x72, 0x65, 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, + 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0xd0, 0x01, 0x0a, + 0x10, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x0a, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, 0x64, 0x12, 0x23, + 0x0a, 0x0d, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x69, 0x64, 0x18, + 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, + 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, + 0xdd, 0x01, 0x0a, 0x10, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x69, + 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x49, + 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, + 0x6c, 0x5f, 0x70, 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x11, 0x63, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x50, 0x75, 0x72, 0x70, 0x6f, + 0x73, 0x65, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, + 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, + 0x47, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x56, 0x69, 0x65, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x3b, 0x5a, 0x39, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, + 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x3b, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -417,23 +665,28 @@ func file_controller_storage_target_store_v1_target_proto_rawDescGZIP() []byte { return file_controller_storage_target_store_v1_target_proto_rawDescData } -var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_controller_storage_target_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_controller_storage_target_store_v1_target_proto_goTypes = []interface{}{ - (*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView - (*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet - (*CredentialLibrary)(nil), // 2: controller.storage.target.store.v1.CredentialLibrary - (*timestamp.Timestamp)(nil), // 3: controller.storage.timestamp.v1.Timestamp + (*TargetView)(nil), // 0: controller.storage.target.store.v1.TargetView + (*TargetHostSet)(nil), // 1: controller.storage.target.store.v1.TargetHostSet + (*CredentialLibrary)(nil), // 2: controller.storage.target.store.v1.CredentialLibrary + (*StaticCredential)(nil), // 3: controller.storage.target.store.v1.StaticCredential + (*CredentialSource)(nil), // 4: controller.storage.target.store.v1.CredentialSource + (*CredentialSourceView)(nil), // 5: controller.storage.target.store.v1.CredentialSourceView + (*timestamp.Timestamp)(nil), // 6: controller.storage.timestamp.v1.Timestamp } var file_controller_storage_target_store_v1_target_proto_depIdxs = []int32{ - 3, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 3, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp - 4, // [4:4] is the sub-list for method output_type - 4, // [4:4] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 6, // 0: controller.storage.target.store.v1.TargetView.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // 1: controller.storage.target.store.v1.TargetView.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // 2: controller.storage.target.store.v1.TargetHostSet.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // 3: controller.storage.target.store.v1.CredentialLibrary.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // 4: controller.storage.target.store.v1.StaticCredential.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // 5: controller.storage.target.store.v1.CredentialSource.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_controller_storage_target_store_v1_target_proto_init() } @@ -478,6 +731,42 @@ func file_controller_storage_target_store_v1_target_proto_init() { return nil } } + file_controller_storage_target_store_v1_target_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StaticCredential); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_target_store_v1_target_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialSource); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_controller_storage_target_store_v1_target_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CredentialSourceView); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -485,7 +774,7 @@ func file_controller_storage_target_store_v1_target_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_controller_storage_target_store_v1_target_proto_rawDesc, NumEnums: 0, - NumMessages: 3, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/internal/target/targettest/target.go b/internal/target/targettest/target.go index 741add0a2..f79dc1536 100644 --- a/internal/target/targettest/target.go +++ b/internal/target/targettest/target.go @@ -230,8 +230,8 @@ func VetForUpdate(ctx context.Context, t target.Target, paths []string) error { return nil } -// VetCredentialLibraries allows for any CredentialLibraries. -func VetCredentialLibraries(_ context.Context, _ []*target.CredentialLibrary) error { +// VetCredentialSources allows for any CredentialLibraries. +func VetCredentialSources(_ context.Context, _ []*target.CredentialLibrary, _ []*target.StaticCredential) error { return nil } diff --git a/internal/target/tcp/register.go b/internal/target/tcp/register.go index db8119c01..ca4d81339 100644 --- a/internal/target/tcp/register.go +++ b/internal/target/tcp/register.go @@ -71,13 +71,18 @@ func (h targetHooks) VetForUpdate(ctx context.Context, t target.Target, paths [] return nil } -// VetCredentialLibraries checks that all of the provided credential libriaries have a CredentialPurpose +// VetCredentialSources checks that all the provided credential sources have a CredentialPurpose // of ApplicationPurpose. Any other CredentialPurpose will result in an error. -func (h targetHooks) VetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error { - const op = "tcp.vetCredentialLibraries" +func (h targetHooks) VetCredentialSources(ctx context.Context, libs []*target.CredentialLibrary, creds []*target.StaticCredential) error { + const op = "tcp.VetCredentialSources" - for _, cl := range cls { - if cl.CredentialPurpose != string(credential.ApplicationPurpose) { + for _, c := range libs { + if c.GetCredentialPurpose() != string(credential.ApplicationPurpose) { + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("tcp.Target only supports credential purpose: %q", credential.ApplicationPurpose)) + } + } + for _, c := range creds { + if c.GetCredentialPurpose() != string(credential.ApplicationPurpose) { return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("tcp.Target only supports credential purpose: %q", credential.ApplicationPurpose)) } } diff --git a/internal/target/tcp/repository_credential_source_test.go b/internal/target/tcp/repository_credential_source_test.go index 0380bfb76..0576feda6 100644 --- a/internal/target/tcp/repository_credential_source_test.go +++ b/internal/target/tcp/repository_credential_source_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" @@ -31,16 +32,23 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { repo, err := target.NewRepository(rw, rw, testKms) require.NoError(t, err) - cs := vault.TestCredentialStores(t, conn, wrapper, staticProj.GetPublicId(), 1)[0] - libs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 3) + csVault := vault.TestCredentialStores(t, conn, wrapper, staticProj.GetPublicId(), 1)[0] + libs := vault.TestCredentialLibraries(t, conn, wrapper, csVault.GetPublicId(), 3) require.Len(t, libs, 3) lib1 := libs[0] lib2 := libs[1] lib3 := libs[2] + csStatic := static.TestCredentialStore(t, conn, wrapper, staticProj.GetPublicId()) + creds := static.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", csStatic.GetPublicId(), staticProj.GetPublicId(), 3) + require.Len(t, creds, 3) + cred1 := creds[0] + cred2 := creds[1] + cred3 := creds[2] + type args struct { targetVersion uint32 - cls []*target.CredentialLibrary + ids target.CredentialSources } tests := []struct { name string @@ -66,18 +74,88 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { wantErrCode: errors.InvalidParameter, }, { - name: "valid-single-source", + name: "valid-single-library", args: args{ targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - }, + ids: target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}, }, wantCredSources: map[string]target.CredentialSource{ - lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ - CredentialLibrary: &store.CredentialLibrary{ - CredentialLibraryId: lib1.PublicId, - CredentialPurpose: string(credential.ApplicationPurpose), + lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + wantErr: false, + }, + { + name: "valid-single-static", + args: args{ + targetVersion: 1, + ids: target.CredentialSources{ApplicationCredentialIds: []string{cred1.PublicId}}, + }, + wantCredSources: map[string]target.CredentialSource{ + cred1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + wantErr: false, + }, + { + name: "valid-multiple-libraries", + args: args{ + targetVersion: 1, + ids: target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}}, + }, + wantCredSources: map[string]target.CredentialSource{ + lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + lib2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib2.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + lib3.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib3.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + wantErr: false, + }, + { + name: "valid-multiple-static", + args: args{ + targetVersion: 1, + ids: target.CredentialSources{ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId, cred3.PublicId}}, + }, + wantCredSources: map[string]target.CredentialSource{ + cred1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + cred2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred2.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + cred3.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred3.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), }, }, }, @@ -87,29 +165,33 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { name: "valid-multiple-sources", args: args{ targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib3.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId, lib1.PublicId, lib2.PublicId}, }, }, wantCredSources: map[string]target.CredentialSource{ - lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ - CredentialLibrary: &store.CredentialLibrary{ - CredentialLibraryId: lib1.PublicId, - CredentialPurpose: string(credential.ApplicationPurpose), + cred1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), }, }, - lib2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ - CredentialLibrary: &store.CredentialLibrary{ - CredentialLibraryId: lib2.PublicId, - CredentialPurpose: string(credential.ApplicationPurpose), + cred2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: cred2.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), }, }, - lib3.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ - CredentialLibrary: &store.CredentialLibrary{ - CredentialLibraryId: lib3.PublicId, - CredentialPurpose: string(credential.ApplicationPurpose), + lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + lib2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: lib2.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), }, }, }, @@ -119,34 +201,27 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { name: "invalid-source-id", args: args{ targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib3.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", "invalid-source-id", credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, cred2.PublicId, "invalid-source-id", lib3.PublicId, cred3.PublicId}, }, }, wantErr: true, wantErrCode: errors.NotSpecificIntegrity, }, { - name: "egress-credential-purpose", + name: "egress-credential-purpose-library", args: args{ targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), - }, + ids: target.CredentialSources{EgressCredentialIds: []string{lib1.PublicId}}, }, wantErr: true, wantErrCode: errors.InvalidParameter, }, { - name: "ingress-credential-purpose", + name: "egress-credential-purpose-static", args: args{ targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose), - }, + ids: target.CredentialSources{EgressCredentialIds: []string{cred1.PublicId}}, }, wantErr: true, wantErrCode: errors.InvalidParameter, @@ -155,9 +230,7 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { name: "bad-version", args: args{ targetVersion: 1000, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - }, + ids: target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}, }, wantErr: true, wantErrCode: errors.VersionMismatch, @@ -170,11 +243,7 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { ctx := context.Background() projTarget := tcp.TestTarget(ctx, t, conn, staticProj.PublicId, tt.name) - for _, cl := range tt.args.cls { - cl.TargetId = projTarget.GetPublicId() - } - - gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.GetPublicId(), tt.args.targetVersion, tt.args.cls) + gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.GetPublicId(), tt.args.targetVersion, tt.args.ids) if tt.wantErr { require.Error(err) assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) @@ -215,22 +284,28 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { ctx := context.Background() projTarget := tcp.TestTarget(ctx, t, conn, staticProj.PublicId, "add-existing") - cl1 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib1.PublicId, credential.ApplicationPurpose) - cl2 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib2.PublicId, credential.ApplicationPurpose) - cl3 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib3.PublicId, credential.ApplicationPurpose) - _, _, gotCredSources, err := repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 1, []*target.CredentialLibrary{cl1}) + ids := target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId}, + } + _, _, gotCredSources, err := repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 1, ids) require.NoError(err) assert.Len(gotCredSources, 1) assert.Equal(lib1.PublicId, gotCredSources[0].Id()) // Adding lib1 again should error - _, _, _, err = repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, []*target.CredentialLibrary{cl1}) + ids = target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId}, + } + _, _, _, err = repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, ids) require.Error(err) assert.True(errors.Match(errors.T(errors.NotUnique), err)) // Adding multiple with lib1 in set should error - _, _, _, err = repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, []*target.CredentialLibrary{cl3, cl2, cl1}) + ids = target.CredentialSources{ + ApplicationCredentialIds: []string{lib3.PublicId, lib2.PublicId, lib1.PublicId}, + } + _, _, _, err = repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, ids) require.Error(err) assert.True(errors.Match(errors.T(errors.NotUnique), err)) @@ -243,8 +318,10 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cl1 := target.TestNewCredentialLibrary("fake-target-id", lib1.PublicId, credential.ApplicationPurpose) - _, _, _, err := repo.AddTargetCredentialSources(context.Background(), "fake-target-id", 1, []*target.CredentialLibrary{cl1}) + ids := target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId}, + } + _, _, _, err := repo.AddTargetCredentialSources(context.Background(), "fake-target-id", 1, ids) require.Error(err) assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) @@ -265,8 +342,10 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { type args struct { targetIdOverride *string targetVersionOverride *uint32 - createCnt int - deleteCnt int + createLibCnt int + createStaticCnt int + deleteLibCnt int + deleteStaticCnt int } tests := []struct { name string @@ -276,27 +355,50 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { wantErrCode errors.Code }{ { - name: "valid", + name: "valid-lib-only", args: args{ - createCnt: 5, - deleteCnt: 5, + createLibCnt: 5, + deleteLibCnt: 5, }, wantRowsDeleted: 5, wantErr: false, }, + { + name: "valid-static-only", + args: args{ + createStaticCnt: 5, + deleteStaticCnt: 5, + }, + wantRowsDeleted: 5, + wantErr: false, + }, + { + name: "valid-mixed", + args: args{ + createLibCnt: 5, + deleteLibCnt: 5, + createStaticCnt: 5, + deleteStaticCnt: 5, + }, + wantRowsDeleted: 10, + wantErr: false, + }, { name: "valid-keeping-some", args: args{ - createCnt: 5, - deleteCnt: 2, + createLibCnt: 5, + deleteLibCnt: 3, + createStaticCnt: 5, + deleteStaticCnt: 2, }, - wantRowsDeleted: 2, + wantRowsDeleted: 5, wantErr: false, }, { name: "no-deletes", args: args{ - createCnt: 5, + createLibCnt: 5, + createStaticCnt: 5, }, wantRowsDeleted: 0, wantErr: true, @@ -306,8 +408,8 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { name: "not-found", args: args{ targetIdOverride: func() *string { id := tcp.TestId(t); return &id }(), - createCnt: 5, - deleteCnt: 5, + createLibCnt: 5, + deleteLibCnt: 5, }, wantRowsDeleted: 0, wantErr: true, @@ -317,8 +419,8 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { name: "missing-target-id", args: args{ targetIdOverride: func() *string { id := ""; return &id }(), - createCnt: 5, - deleteCnt: 5, + createLibCnt: 5, + deleteLibCnt: 5, }, wantRowsDeleted: 0, wantErr: true, @@ -328,8 +430,8 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { name: "zero-version", args: args{ targetVersionOverride: func() *uint32 { v := uint32(0); return &v }(), - createCnt: 5, - deleteCnt: 5, + createLibCnt: 5, + deleteLibCnt: 5, }, wantRowsDeleted: 0, wantErr: true, @@ -339,38 +441,47 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { name: "bad-version", args: args{ targetVersionOverride: func() *uint32 { v := uint32(1000); return &v }(), - createCnt: 5, - deleteCnt: 5, + createLibCnt: 5, + deleteLibCnt: 5, }, wantRowsDeleted: 0, wantErr: true, wantErrCode: errors.VersionMismatch, }, } - css := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) + csVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) + csStatic := static.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), len(tests)) for i, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cs := css[i] + csv := csVault[i] + css := csStatic[i] ctx := context.Background() tar := tcp.TestTarget(ctx, t, conn, proj.PublicId, tt.name) - cls := make([]*target.CredentialLibrary, 0, tt.args.createCnt) - if tt.args.createCnt > 0 { - credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.PublicId, tt.args.createCnt) - for _, cl := range credLibs { - cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) - } + var ids target.CredentialSources + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, csv.PublicId, tt.args.createLibCnt) + for _, cl := range credLibs { + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, cl.GetPublicId()) + } + creds := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", css.PublicId, proj.GetPublicId(), tt.args.createStaticCnt) + for _, c := range creds { + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, c.GetPublicId()) } - _, _, addedCredSources, err := repo.AddTargetCredentialSources(ctx, tar.GetPublicId(), 1, cls) - require.NoError(err) - assert.Equal(tt.args.createCnt, len(addedCredSources)) - deleteCredSources := make([]*target.CredentialLibrary, 0, tt.args.deleteCnt) - for i := 0; i < tt.args.deleteCnt; i++ { - deleteCredSources = append(deleteCredSources, cls[i]) + _, _, addedCredSources, err := repo.AddTargetCredentialSources(ctx, tar.GetPublicId(), 1, ids) + require.NoError(err) + assert.Equal(tt.args.createLibCnt+tt.args.createStaticCnt, len(addedCredSources)) + + var deleteIds target.CredentialSources + for i := 0; i < tt.args.deleteLibCnt; i++ { + deleteIds.ApplicationCredentialIds = append(deleteIds.ApplicationCredentialIds, credLibs[i].GetPublicId()) } + for i := 0; i < tt.args.deleteStaticCnt; i++ { + deleteIds.ApplicationCredentialIds = append(deleteIds.ApplicationCredentialIds, creds[i].GetPublicId()) + } + var targetId string switch { case tt.args.targetIdOverride != nil: @@ -385,7 +496,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { default: targetVersion = 2 } - deletedRows, err := repo.DeleteTargetCredentialSources(ctx, targetId, targetVersion, deleteCredSources) + deletedRows, err := repo.DeleteTargetCredentialSources(ctx, targetId, targetVersion, deleteIds) if tt.wantErr { require.Error(err) assert.Equal(0, deletedRows) @@ -413,22 +524,28 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { ctx := context.Background() projTarget := tcp.TestTarget(ctx, t, conn, proj.PublicId, "add-existing") - cl1 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib1.PublicId, credential.ApplicationPurpose) - cl2 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib2.PublicId, credential.ApplicationPurpose) - cl3 := target.TestNewCredentialLibrary(projTarget.GetPublicId(), lib3.PublicId, credential.ApplicationPurpose) - _, _, gotCredSources, err := repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 1, []*target.CredentialLibrary{cl1, cl2}) + ids := target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.GetPublicId(), lib2.GetPublicId()}, + } + _, _, gotCredSources, err := repo.AddTargetCredentialSources(ctx, projTarget.GetPublicId(), 1, ids) require.NoError(err) assert.Len(gotCredSources, 2) // Deleting an unassociated source should return an error - delCount, err := repo.DeleteTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, []*target.CredentialLibrary{cl3}) + delCount, err := repo.DeleteTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, + target.CredentialSources{ + ApplicationCredentialIds: []string{lib3.GetPublicId()}, + }) require.Error(err) assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) assert.Equal(0, delCount) // Deleting sources which includes an unassociated source should return an error - delCount, err = repo.DeleteTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, []*target.CredentialLibrary{cl1, cl2, cl3}) + delCount, err = repo.DeleteTargetCredentialSources(ctx, projTarget.GetPublicId(), 2, + target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.GetPublicId(), lib2.GetPublicId(), lib3.GetPublicId()}, + }) require.Error(err) assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) assert.Equal(0, delCount) @@ -452,31 +569,40 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { iamRepo := iam.TestRepo(t, conn, wrapper) _, proj := iam.TestScopes(t, iamRepo) - cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] - credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) + storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) lib1 := credLibs[0] lib2 := credLibs[1] - setupFn := func(tar target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) { - credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 10) - cls := make([]*target.CredentialLibrary, 0, len(credLibs)) + storeStatic := static.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] + credsStatic := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", storeStatic.GetPublicId(), proj.GetPublicId(), 2) + cred1 := credsStatic[0] + cred2 := credsStatic[1] + + setupFn := func(tar target.Target) ([]target.CredentialSource, target.CredentialSources) { + credLibs := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 5) + var ids target.CredentialSources for _, cl := range credLibs { - cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, cl.GetPublicId()) + } + creds := static.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", storeStatic.GetPublicId(), proj.GetPublicId(), 5) + for _, cred := range creds { + ids.ApplicationCredentialIds = append(ids.ApplicationCredentialIds, cred.GetPublicId()) } - _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) + _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, ids) require.NoError(t, err) require.Equal(t, 10, len(created)) - return created, cls + return created, ids } type args struct { - targetVersion uint32 - cls []*target.CredentialLibrary - addToOrigLibs bool + targetVersion uint32 + ids target.CredentialSources + addToOrigSources bool } tests := []struct { name string - setup func(target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) + setup func(target.Target) ([]target.CredentialSource, target.CredentialSources) args args wantAffectedRows int wantErr bool @@ -487,7 +613,6 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{}, }, wantErr: false, wantAffectedRows: 10, @@ -496,37 +621,60 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { name: "no-change", setup: setupFn, args: args{ - targetVersion: 2, - cls: []*target.CredentialLibrary{}, - addToOrigLibs: true, + targetVersion: 2, + addToOrigSources: true, }, wantErr: false, wantAffectedRows: 0, }, { - name: "add-cred-sources", + name: "add-cred-library", setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: false, wantAffectedRows: 2, }, + { + name: "add-cred-static", + setup: setupFn, + args: args{ + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId}, + }, + addToOrigSources: true, + }, + wantErr: false, + wantAffectedRows: 2, + }, + { + name: "add-cred-lib-and-static", + setup: setupFn, + args: args{ + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, lib1.PublicId, cred2.PublicId, lib2.PublicId}, + }, + addToOrigSources: true, + }, + wantErr: false, + wantAffectedRows: 4, + }, { name: "zero version", setup: setupFn, args: args{ targetVersion: 0, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: true, wantErrCode: errors.InvalidParameter, @@ -536,11 +684,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 1000, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: true, + addToOrigSources: true, }, wantErr: true, wantErrCode: errors.VersionMismatch, @@ -550,38 +697,39 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), - target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{lib1.PublicId, lib2.PublicId}, }, - addToOrigLibs: false, + addToOrigSources: false, }, wantErr: false, wantAffectedRows: 12, }, { - name: "egress-credential-purpose", + name: "remove existing and add cred static", setup: setupFn, args: args{ - targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, cred2.PublicId}, }, + addToOrigSources: false, }, - wantErr: true, - wantErrCode: errors.InvalidParameter, + wantErr: false, + wantAffectedRows: 12, }, { - name: "ingress-credential-purpose", + name: "remove existing and add cred static and lib", setup: setupFn, args: args{ - targetVersion: 1, - cls: []*target.CredentialLibrary{ - target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose), + targetVersion: 2, + ids: target.CredentialSources{ + ApplicationCredentialIds: []string{cred1.PublicId, lib2.PublicId}, }, + addToOrigSources: false, }, - wantErr: true, - wantErrCode: errors.InvalidParameter, + wantErr: false, + wantAffectedRows: 12, }, } for _, tt := range tests { @@ -592,23 +740,28 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { tar := tcp.TestTarget(ctx, t, conn, proj.PublicId, tt.name) var origCredSources []target.CredentialSource - var origCredLibraries []*target.CredentialLibrary - if tt.setup != nil { - origCredSources, origCredLibraries = tt.setup(tar) - } - wantCredSources := make(map[string]target.CredentialSource) - for _, cl := range tt.args.cls { - cl.TargetId = tar.GetPublicId() - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ - CredentialLibrary: cl.CredentialLibrary, + if tt.setup != nil { + var origCredIds target.CredentialSources + origCredSources, origCredIds = tt.setup(tar) + + if tt.args.addToOrigSources { + tt.args.ids.ApplicationCredentialIds = append(tt.args.ids.ApplicationCredentialIds, origCredIds.ApplicationCredentialIds...) + tt.args.ids.EgressCredentialIds = append(tt.args.ids.EgressCredentialIds, origCredIds.EgressCredentialIds...) } } - if tt.args.addToOrigLibs { - tt.args.cls = append(tt.args.cls, origCredLibraries...) - for _, cl := range origCredLibraries { - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ - CredentialLibrary: cl.CredentialLibrary, + + byPurpose := map[credential.Purpose][]string{ + credential.ApplicationPurpose: tt.args.ids.ApplicationCredentialIds, + credential.EgressPurpose: tt.args.ids.EgressCredentialIds, + } + for purpose, ids := range byPurpose { + for _, id := range ids { + wantCredSources[id+"_"+string(purpose)] = &target.TargetCredentialSource{ + CredentialSource: &store.CredentialSource{ + CredentialSourceId: id, + CredentialPurpose: string(purpose), + }, } } } @@ -617,7 +770,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(origCredSources, lookupCredSources) - _, got, affectedRows, err := repo.SetTargetCredentialSources(ctx, tar.GetPublicId(), tt.args.targetVersion, tt.args.cls) + _, gotSources, affectedRows, err := repo.SetTargetCredentialSources(ctx, tar.GetPublicId(), tt.args.targetVersion, tt.args.ids) if tt.wantErr { require.Error(err) assert.Equal(0, affectedRows) @@ -628,9 +781,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(tt.wantAffectedRows, affectedRows) - assert.Equal(len(wantCredSources), len(got)) - - for _, cs := range got { + for _, cs := range gotSources { w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] assert.True(ok, "got unexpected credentialsource %v", cs) assert.Equal(w.Id(), cs.Id()) @@ -648,8 +799,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("missing-target-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cl1 := target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, []*target.CredentialLibrary{cl1}) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, + target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error()) @@ -657,8 +808,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - cl1 := target.TestNewCredentialLibrary("fake-target-id", lib1.PublicId, credential.ApplicationPurpose) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, []*target.CredentialLibrary{cl1}) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, + target.CredentialSources{ApplicationCredentialIds: []string{lib1.PublicId}}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) diff --git a/internal/target/tcp/repository_tcp_target_test.go b/internal/target/tcp/repository_tcp_target_test.go index cb4bafec7..eaa690d48 100644 --- a/internal/target/tcp/repository_tcp_target_test.go +++ b/internal/target/tcp/repository_tcp_target_test.go @@ -465,7 +465,7 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { for _, cl := range credSources { afterUpdateIds = append(afterUpdateIds, cl.Id()) } - assert.Equal(testClIds, afterUpdateIds) + assert.ElementsMatch(testClIds, afterUpdateIds) switch tt.name { case "valid-no-op": diff --git a/internal/target/tcp/testing.go b/internal/target/tcp/testing.go index 0e73faa38..be1647348 100644 --- a/internal/target/tcp/testing.go +++ b/internal/target/tcp/testing.go @@ -45,6 +45,15 @@ func TestTarget(ctx context.Context, t testing.TB, conn *db.DB, scopeId, name st err := rw.CreateItems(context.Background(), newCredLibs) require.NoError(err) } + if len(opts.WithStaticCredentials) > 0 { + newCreds := make([]interface{}, 0, len(opts.WithStaticCredentials)) + for _, c := range opts.WithStaticCredentials { + c.TargetId = tar.GetPublicId() + newCreds = append(newCreds, c) + } + err := rw.CreateItems(context.Background(), newCreds) + require.NoError(err) + } return tar } diff --git a/internal/target/tcp/testing_test.go b/internal/target/tcp/testing_test.go index 9716a3f52..bcc402f5a 100644 --- a/internal/target/tcp/testing_test.go +++ b/internal/target/tcp/testing_test.go @@ -45,7 +45,7 @@ func Test_TestTcpTarget(t *testing.T) { for _, s := range foundSources { foundIds = append(foundIds, s.Id()) } - require.Equal(sets, foundIds) + require.ElementsMatch(sets, foundIds) } func Test_TestCredentialLibrary(t *testing.T) { @@ -80,5 +80,5 @@ func Test_TestCredentialLibrary(t *testing.T) { for _, s := range foundSources { foundIds = append(foundIds, s.Id()) } - require.Equal(libIds, foundIds) + require.ElementsMatch(libIds, foundIds) } diff --git a/internal/target/testing.go b/internal/target/testing.go index 964a66dd1..b7776733d 100644 --- a/internal/target/testing.go +++ b/internal/target/testing.go @@ -23,6 +23,19 @@ func TestNewCredentialLibrary(targetId, credentialLibraryId string, purpose cred } } +// TestNewStaticCredential creates a new in memory StaticCredential +// representing the relationship between targetId and credentialId with +// the given purpose. +func TestNewStaticCredential(targetId, credentialId string, purpose credential.Purpose) *StaticCredential { + return &StaticCredential{ + StaticCredential: &store.StaticCredential{ + TargetId: targetId, + CredentialId: credentialId, + CredentialPurpose: string(purpose), + }, + } +} + // TestCredentialLibrary creates a CredentialLibrary for targetId and // libraryId with the credential purpose of application. func TestCredentialLibrary(t testing.TB, conn *db.DB, targetId, libraryId string) *CredentialLibrary { diff --git a/internal/tests/api/targets/target_test.go b/internal/tests/api/targets/target_test.go index 9943b702f..d6b4810e5 100644 --- a/internal/tests/api/targets/target_test.go +++ b/internal/tests/api/targets/target_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/credentiallibraries" + "github.com/hashicorp/boundary/api/credentials" "github.com/hashicorp/boundary/api/credentialstores" "github.com/hashicorp/boundary/api/hostcatalogs" "github.com/hashicorp/boundary/api/hostsets" @@ -66,11 +67,7 @@ func TestHostSetASD(t *testing.T) { assert.Empty(tar.Item.HostSetIds) } -func defaultCredLibrary(r *credentiallibraries.CredentialLibrary) *targets.CredentialLibrary { - return &targets.CredentialLibrary{Id: r.Id, CredentialStoreId: r.CredentialStoreId} -} - -func TestCredentialLibraryASD(t *testing.T) { +func TestCredentialSourcesASD(t *testing.T) { assert, require := assert.New(t), require.New(t) tc := controller.NewTestController(t, nil) defer tc.Shutdown() @@ -82,44 +79,85 @@ func TestCredentialLibraryASD(t *testing.T) { client := tc.Client().Clone() client.SetToken(token.Token) - cs, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), + csVault, err := credentialstores.NewClient(client).Create(tc.Context(), "vault", proj.GetPublicId(), credentialstores.WithVaultCredentialStoreAddress(vaultServ.Addr), credentialstores.WithVaultCredentialStoreToken(vaultTok)) require.NoError(err) - require.NotNil(cs) + require.NotNil(csVault) lClient := credentiallibraries.NewClient(client) - r1, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1")) + lib1, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something1")) require.NoError(err) - require.NotNil(r1) + require.NotNil(lib1) - r2, err := lClient.Create(tc.Context(), cs.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2")) + lib2, err := lClient.Create(tc.Context(), csVault.Item.Id, credentiallibraries.WithVaultCredentialLibraryPath("something2")) require.NoError(err) - require.NotNil(r1) + require.NotNil(lib2) + + csStatic, err := credentialstores.NewClient(client).Create(tc.Context(), "static", proj.GetPublicId()) + require.NoError(err) + require.NotNil(csStatic) + + cClient := credentials.NewClient(client) + cOpts := []credentials.Option{credentials.WithUsernamePasswordCredentialUsername("user"), credentials.WithUsernamePasswordCredentialPassword("pass")} + cred, err := cClient.Create(tc.Context(), "username_password", csStatic.Item.Id, cOpts...) + require.NoError(err) + require.NotNil(cred) tarClient := targets.NewClient(client) tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"), targets.WithTcpTargetDefaultPort(2)) require.NoError(err) require.NotNil(tar) - assert.Empty(tar.Item.ApplicationCredentialLibraryIds) + assert.Empty(tar.Item.ApplicationCredentialSourceIds) - tar, err = tarClient.AddCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, targets.WithApplicationCredentialLibraryIds([]string{r1.Item.Id})) + // Add first Vault library + tar, err = tarClient.AddCredentialSources(tc.Context(), tar.Item.Id, tar.Item.Version, + targets.WithApplicationCredentialSourceIds([]string{lib1.Item.Id})) require.NoError(err) require.NotNil(tar) - assert.ElementsMatch(tar.Item.ApplicationCredentialLibraryIds, []string{r1.Item.Id}) - assert.ElementsMatch(tar.Item.ApplicationCredentialLibraries, []*targets.CredentialLibrary{defaultCredLibrary(r1.Item)}) + assert.ElementsMatch(tar.Item.ApplicationCredentialSourceIds, []string{lib1.Item.Id}) + assert.ElementsMatch(tar.Item.ApplicationCredentialSources, []*targets.CredentialSource{ + { + Id: lib1.Item.Id, + CredentialStoreId: csVault.Item.Id, + }, + }) - tar, err = tarClient.SetCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, - targets.WithApplicationCredentialLibraryIds([]string{r2.Item.Id})) + // Set second Vault library and a static credential + tar, err = tarClient.SetCredentialSources(tc.Context(), tar.Item.Id, tar.Item.Version, + targets.WithApplicationCredentialSourceIds([]string{lib2.Item.Id, cred.Item.Id})) require.NoError(err) require.NotNil(tar) - assert.ElementsMatch(tar.Item.ApplicationCredentialLibraryIds, []string{r2.Item.Id}) - assert.ElementsMatch(tar.Item.ApplicationCredentialLibraries, []*targets.CredentialLibrary{defaultCredLibrary(r2.Item)}) + assert.ElementsMatch(tar.Item.ApplicationCredentialSourceIds, []string{lib2.Item.Id, cred.Item.Id}) + assert.ElementsMatch(tar.Item.ApplicationCredentialSources, []*targets.CredentialSource{ + { + Id: lib2.Item.Id, + CredentialStoreId: csVault.Item.Id, + }, + { + Id: cred.Item.Id, + CredentialStoreId: csStatic.Item.Id, + }, + }) - tar, err = tarClient.RemoveCredentialLibraries(tc.Context(), tar.Item.Id, tar.Item.Version, - targets.WithApplicationCredentialLibraryIds([]string{r2.Item.Id})) + // Remove second Vault library + tar, err = tarClient.RemoveCredentialSources(tc.Context(), tar.Item.Id, tar.Item.Version, + targets.WithApplicationCredentialSourceIds([]string{lib2.Item.Id})) require.NoError(err) require.NotNil(tar) - assert.Empty(tar.Item.ApplicationCredentialLibraryIds) + assert.ElementsMatch(tar.Item.ApplicationCredentialSourceIds, []string{cred.Item.Id}) + assert.ElementsMatch(tar.Item.ApplicationCredentialSources, []*targets.CredentialSource{ + { + Id: cred.Item.Id, + CredentialStoreId: csStatic.Item.Id, + }, + }) + + // Set empty credential sources + tar, err = tarClient.SetCredentialSources(tc.Context(), tar.Item.Id, tar.Item.Version, + targets.WithApplicationCredentialSourceIds([]string{})) + require.NoError(err) + require.NotNil(tar) + assert.Empty(tar.Item.ApplicationCredentialSourceIds) } func TestList(t *testing.T) { diff --git a/internal/tests/cli/boundary/_target_credential_sources.bash b/internal/tests/cli/boundary/_target_credential_sources.bash new file mode 100644 index 000000000..b2097ca75 --- /dev/null +++ b/internal/tests/cli/boundary/_target_credential_sources.bash @@ -0,0 +1,52 @@ +load _authorized_actions + +function add_target_application_credential_sources() { + for i in "${@:2}" + do + cred+="-application-credential-source $i " + done + + boundary targets add-credential-sources -id $1 $cred +} + +function remove_target_application_credential_sources() { + for i in "${@:2}" + do + cred+="-application-credential-source $i " + done + + boundary targets remove-credential-sources -id $1 $cred +} + +function set_target_application_credential_sources() { + for i in "${@:2}" + do + cred+="-application-credential-source $i " + done + + boundary targets set-credential-sources -id $1 $cred +} + +function validate_credential_sources() { + targetData=$(boundary targets read -id $1 -format json) + for i in "${@:2}" + do + if ! echo "$targetData" | grep -q $i; then + echo "Credential source id '$i' not found on target" + echo "$targetData" + exit 1 + fi + done +} + +function validate_credential_sources_not_present() { + targetData=$(boundary targets read -id $1 -format json) + for i in "${@:2}" + do + if echo "$targetData" | grep -q $i; then + echo "Credential source id '$i' unexpectedly found on target" + echo "$targetData" + exit 1 + fi + done +} diff --git a/internal/tests/cli/boundary/target_credential_sources.bats b/internal/tests/cli/boundary/target_credential_sources.bats new file mode 100644 index 000000000..4da9204e8 --- /dev/null +++ b/internal/tests/cli/boundary/target_credential_sources.bats @@ -0,0 +1,174 @@ +#!/usr/bin/env bats + +load _auth +load _connect +load _targets +load _helpers +load _credential_stores +load _credentials +load _target_credential_sources + +export NEW_STORE='test-for-add-credential-sources' +export NEW_CREDENTIAL='first-credential' +export NEW_CREDENTIAL1='second-credential' +export NEW_CREDENTIAL2='third-credential' + +@test "boundary/login: can login as admin user" { + run login $DEFAULT_LOGIN + [ "$status" -eq 0 ] +} + +@test "boundary/credential-stores: can create $NEW_STORE static store in default project" { + run create_static_credential_store $NEW_STORE $DEFAULT_P_ID + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can create $NEW_CREDENTIAL credential in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_credential $NEW_CREDENTIAL $csid 'username' 'password' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: can add $NEW_CREDENTIAL credential source" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run add_target_application_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate only $NEW_CREDENTIAL credential source present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run validate_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: cannot add duplicate credential source" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run add_target_application_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 1 ] +} + +@test "boundary/target: can delete $NEW_CREDENTIAL credential source" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run remove_target_application_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate $NEW_CREDENTIAL credential source removed" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run validate_credential_sources_not_present $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/credentials: can create additional credentials in $NEW_STORE store" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + run create_username_password_credential $NEW_CREDENTIAL1 $csid 'username' 'password' + echo "$output" + [ "$status" -eq 0 ] + + run create_username_password_credential $NEW_CREDENTIAL2 $csid 'username' 'password' + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: can add multiple credential sources" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run add_target_application_credential_sources $DEFAULT_TARGET $cid $cid1 $cid2 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate added credential sources present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run validate_credential_sources $DEFAULT_TARGET $cid $cid1 $cid2 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: can delete multiple credential sources" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + run remove_target_application_credential_sources $DEFAULT_TARGET $cid $cid1 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate $NEW_CREDENTIAL2 credential source present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run validate_credential_sources $DEFAULT_TARGET $cid2 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate deleted credential sources not present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL $csid) + run validate_credential_sources_not_present $DEFAULT_TARGET $cid $cid1 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: can set multiple credential sources" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run set_target_application_credential_sources $DEFAULT_TARGET $cid $cid1 $cid2 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate set credential sources present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run validate_credential_sources $DEFAULT_TARGET $cid2 + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: can set just $NEW_CREDENTIAL credential source" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run set_target_application_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate $NEW_CREDENTIAL credential source present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid=$(credential_id $NEW_CREDENTIAL $csid) + run validate_credential_sources $DEFAULT_TARGET $cid + echo "$output" + [ "$status" -eq 0 ] +} + +@test "boundary/target: validate not set credential sources not present" { + local csid=$(credential_store_id $NEW_STORE $DEFAULT_P_ID) + local cid1=$(credential_id $NEW_CREDENTIAL1 $csid) + local cid2=$(credential_id $NEW_CREDENTIAL2 $csid) + run validate_credential_sources_not_present $DEFAULT_TARGET $cid1 $cid2 + echo "$output" + [ "$status" -eq 0 ] +}