From 2cc58ff9014deb3cf48d1d2fe158467978ff2a0e Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Tue, 26 Oct 2021 09:57:22 -0400 Subject: [PATCH 1/2] refact(target): Change repository to take CredentialLibraries This change removes the hard-coded credential purpose for the Credential Libraries. It also moves the logic of creating CredentialLibraries from CredentialSources out of the repository and into the targets.Service. This refactor will allow for additional credential purposes to be supported in both the service and the repository. --- Makefile | 1 + internal/credential/credential.go | 11 + .../migrations/oss/postgres_16_05_test.go | 15 +- .../target/targettest/store/v1/target.proto | 73 +++++ .../handlers/targets/target_service.go | 48 ++- .../handlers/targets/target_service_test.go | 33 +- internal/session/repository_session_test.go | 23 +- internal/target/credential_library.go | 4 +- internal/target/credential_library_test.go | 2 +- internal/target/options.go | 10 +- internal/target/options_test.go | 34 +- internal/target/query.go | 3 +- .../target/repository_credential_source.go | 119 ++++--- .../repository_credential_source_test.go | 254 +++++++++++++++ internal/target/targettest/store/target.pb.go | 307 ++++++++++++++++++ internal/target/targettest/target.go | 262 +++++++++++++++ .../tcp/repository_credential_source_test.go | 88 +++-- .../target/tcp/repository_tcp_target_test.go | 26 +- internal/target/tcp/testing.go | 11 +- internal/target/testing.go | 22 +- 20 files changed, 1213 insertions(+), 133 deletions(-) create mode 100644 internal/proto/local/controller/storage/target/targettest/store/v1/target.proto create mode 100644 internal/target/repository_credential_source_test.go create mode 100644 internal/target/targettest/store/target.pb.go create mode 100644 internal/target/targettest/target.go diff --git a/Makefile b/Makefile index 51296c11e..41aef097c 100644 --- a/Makefile +++ b/Makefile @@ -129,6 +129,7 @@ protobuild: @protoc-go-inject-tag -input=./internal/kms/store/session_key.pb.go @protoc-go-inject-tag -input=./internal/kms/store/oidc_key.pb.go @protoc-go-inject-tag -input=./internal/target/store/target.pb.go + @protoc-go-inject-tag -input=./internal/target/targettest/store/target.pb.go @protoc-go-inject-tag -input=./internal/target/tcp/store/target.pb.go @protoc-go-inject-tag -input=./internal/auth/oidc/store/oidc.pb.go @protoc-go-inject-tag -input=./internal/scheduler/job/store/job.pb.go diff --git a/internal/credential/credential.go b/internal/credential/credential.go index 98b311637..5c337ae21 100644 --- a/internal/credential/credential.go +++ b/internal/credential/credential.go @@ -28,6 +28,10 @@ type Library interface { // Purpose is the purpose of the credential. type Purpose string +func (p Purpose) String() string { + return string(p) +} + // Credential purpose values. const ( // ApplicationPurpose is a credential used for application specific @@ -45,6 +49,13 @@ const ( EgressPurpose Purpose = "egress" ) +// ValidPurposes are the set of all credential Purposes. +var ValidPurposes = []Purpose{ + ApplicationPurpose, + IngressPurpose, + EgressPurpose, +} + // SecretData represents secret data. type SecretData interface{} diff --git a/internal/db/schema/migrations/oss/postgres_16_05_test.go b/internal/db/schema/migrations/oss/postgres_16_05_test.go index d2c279751..b63444085 100644 --- a/internal/db/schema/migrations/oss/postgres_16_05_test.go +++ b/internal/db/schema/migrations/oss/postgres_16_05_test.go @@ -165,16 +165,21 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrappe ctx := context.Background() stores := vault.TestCredentialStores(t, conn, wrapper, tar.GetScopeId(), 1) - libs := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) + libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) + libs := []*target.CredentialLibrary{ + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose), + } targetRepo, err := target.NewRepository(rw, rw, kms) require.NoError(t, err) - _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), []string{libs[0].PublicId, libs[1].PublicId}) + _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs) require.NoError(t, err) creds := []*session.DynamicCredential{ - session.NewDynamicCredential(libs[0].GetPublicId(), credential.ApplicationPurpose), - session.NewDynamicCredential(libs[0].GetPublicId(), credential.IngressPurpose), - session.NewDynamicCredential(libs[1].GetPublicId(), credential.EgressPurpose), + session.NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose), + session.NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose), + session.NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose), } return creds } diff --git a/internal/proto/local/controller/storage/target/targettest/store/v1/target.proto b/internal/proto/local/controller/storage/target/targettest/store/v1/target.proto new file mode 100644 index 000000000..b9471c819 --- /dev/null +++ b/internal/proto/local/controller/storage/target/targettest/store/v1/target.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package controller.storage.target.targettest.store.v1; +option go_package = "github.com/hashicorp/boundary/internal/target/targettest/store;store"; + +import "controller/storage/timestamp/v1/timestamp.proto"; +import "controller/custom_options/v1/options.proto"; + +message Target { + // public_id is used to access the targettest.Target via an API + // @inject_tag: gorm:"primary_key" + string public_id = 10; + + // scope id for the targettest.Target + // @inject_tag: `gorm:"default:null"` + string scope_id = 20; + + // name is the optional friendly name used to + // access the targettest.Target via an API + // @inject_tag: `gorm:"default:null"` + string name = 30 + [(custom_options.v1.mask_mapping) = { this: "name" that: "name" }]; + + // description of the targettest.Target + // @inject_tag: `gorm:"default:null"` + string description = 40 [(custom_options.v1.mask_mapping) = { + this: "description" + that: "description" + }]; + + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp create_time = 50; + + // update_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + timestamp.v1.Timestamp update_time = 60; + + // version allows optimistic locking of the targettest.Target when modifying the + // targettest.Target + // @inject_tag: `gorm:"default:null"` + uint32 version = 70; + + // default port of the targettest.Target + // @inject_tag: `gorm:"default:null"` + uint32 default_port = 80 [(custom_options.v1.mask_mapping) = { + this: "DefaultPort" + that: "attributes.default_port" + }]; + + // Maximum total lifetime of a created session, in seconds + // @inject_tag: `gorm:"default:null"` + uint32 session_max_seconds = 100 [(custom_options.v1.mask_mapping) = { + this: "SessionMaxSeconds" + that: "session_max_seconds" + }]; + + // Maximum number of connections in a session + // @inject_tag: `gorm:"default:null"` + int32 session_connection_limit = 110 [(custom_options.v1.mask_mapping) = { + this: "SessionConnectionLimit" + that: "session_connection_limit" + }]; + + // A boolean expression that allows filtering the workers that can handle a session + // @inject_tag: `gorm:"default:null"` + string worker_filter = 120 [(custom_options.v1.mask_mapping) = { + this: "WorkerFilter" + that: "worker_filter" + }]; +} + + diff --git a/internal/servers/controller/handlers/targets/target_service.go b/internal/servers/controller/handlers/targets/target_service.go index 78c1261de..4c9f9eb1b 100644 --- a/internal/servers/controller/handlers/targets/target_service.go +++ b/internal/servers/controller/handlers/targets/target_service.go @@ -1346,12 +1346,17 @@ func (s Service) removeHostSourcesInRepo(ctx context.Context, targetId string, h return out, hs, cl, nil } -func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string, ids []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { +func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string, applicationIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } - out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, strutil.RemoveDuplicates(ids, false)) + credLibs, err := createCredLibs(targetId, applicationIds, nil, nil) + if err != nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + } + + out, hs, credSources, err := repo.AddTargetCredentialSources(ctx, targetId, version, credLibs) 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) @@ -1362,13 +1367,19 @@ func (s Service) addCredentialSourcesInRepo(ctx context.Context, targetId string return out, hs, credSources, nil } -func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string, ids []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { +func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string, applicationIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).setCredentialSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } - _, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, strutil.RemoveDuplicates(ids, false)) + + credLibs, err := createCredLibs(targetId, applicationIds, nil, nil) + if err != nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + } + + _, _, _, err = repo.SetTargetCredentialSources(ctx, targetId, version, credLibs) 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) @@ -1384,13 +1395,18 @@ func (s Service) setCredentialSourcesInRepo(ctx context.Context, targetId string return out, hs, credSources, nil } -func (s Service) removeCredentialSourcesInRepo(ctx context.Context, targetId string, ids []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { +func (s Service) removeCredentialSourcesInRepo(ctx context.Context, targetId string, applicationIds []string, version uint32) (target.Target, []target.HostSource, []target.CredentialSource, error) { const op = "targets.(Service).removeCredentialSourcesInRepo" repo, err := s.repoFn() if err != nil { return nil, nil, nil, err } - _, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, strutil.RemoveDuplicates(ids, false)) + + credLibs, err := createCredLibs(targetId, applicationIds, nil, nil) + if err != nil { + return nil, nil, nil, handlers.ApiErrorWithCodeAndMessage(codes.Internal, "Unable to set credential sources in target: %v.", err) + } + _, err = repo.DeleteTargetCredentialSources(ctx, targetId, version, credLibs) 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) @@ -2003,3 +2019,23 @@ 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 +} diff --git a/internal/servers/controller/handlers/targets/target_service_test.go b/internal/servers/controller/handlers/targets/target_service_test.go index 700ad493b..8c77191df 100644 --- a/internal/servers/controller/handlers/targets/target_service_test.go +++ b/internal/servers/controller/handlers/targets/target_service_test.go @@ -14,6 +14,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/hashicorp/boundary/internal/authtoken" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" @@ -1704,13 +1705,13 @@ func TestAddTargetLibraries(t *testing.T) { }, { name: "Add library on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for libraries", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), addLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, { name: "Add duplicated libraries on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated for libraries", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated for libraries", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), addLibraries: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultLibraryIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, @@ -1840,19 +1841,19 @@ func TestSetTargetLibraries(t *testing.T) { }, { name: "Set on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setLibraries: []string{cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Set duplicate libraries on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setLibraries: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultLibraryIds: []string{cls[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setLibraries: []string{}, resultLibraryIds: nil, }, @@ -1966,13 +1967,13 @@ func TestRemoveTargetLibraries(t *testing.T) { }, { name: "Remove 1 of 2 libraries", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), removeLibs: []string{cls[1].GetPublicId()}, resultLibIds: []string{cls[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 libraries", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), removeLibs: []string{ cls[1].GetPublicId(), cls[1].GetPublicId(), }, @@ -1980,7 +1981,7 @@ func TestRemoveTargetLibraries(t *testing.T) { }, { name: "Remove all libraries from target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), removeLibs: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), }, @@ -2101,13 +2102,13 @@ func TestAddTargetSources(t *testing.T) { }, { name: "Add source on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated for 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()}, }, { name: "Add duplicated sources on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicated for sources", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(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()}, }, @@ -2237,19 +2238,19 @@ func TestSetTargetCredentialSources(t *testing.T) { }, { name: "Set on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set duplicate sources on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "duplicate", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set empty on populated target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialSources([]string{cls[0].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose)})), setCredentialSources: []string{}, resultCredentialSourceIds: nil, }, @@ -2363,13 +2364,13 @@ func TestRemoveTargetCredentialSources(t *testing.T) { }, { name: "Remove 1 of 2 sources", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove partial", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(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 1 duplicate set of 2 sources", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove duplicate", 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(), }, @@ -2377,7 +2378,7 @@ func TestRemoveTargetCredentialSources(t *testing.T) { }, { name: "Remove all sources from target", - tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialSources([]string{cls[0].GetPublicId(), cls[1].GetPublicId()})), + tar: tcp.TestTarget(t, conn, proj.GetPublicId(), "remove all", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.ApplicationPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.ApplicationPurpose)})), removeCredentialSources: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), }, diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index 68e9076f1..f2107a71d 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -1649,21 +1649,28 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, wrapper wrapping.Wra rw := db.New(conn) ctx := context.Background() - stores := vault.TestCredentialStores(t, conn, wrapper, params.ScopeId, 1) - libs := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) kms := kms.TestKms(t, conn, wrapper) targetRepo, err := target.NewRepository(rw, rw, kms) require.NoError(err) - tcpTarget, _, _, err := targetRepo.LookupTarget(ctx, params.TargetId) + tar, _, _, err := targetRepo.LookupTarget(ctx, params.TargetId) require.NoError(err) - require.NotNil(tcpTarget) - _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tcpTarget.GetPublicId(), tcpTarget.GetVersion(), []string{libs[0].PublicId, libs[1].PublicId}) + require.NotNil(tar) + + 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(), credential.ApplicationPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose), + } + + _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs) require.NoError(err) creds := []*DynamicCredential{ - NewDynamicCredential(libs[0].GetPublicId(), credential.ApplicationPurpose), - NewDynamicCredential(libs[0].GetPublicId(), credential.IngressPurpose), - NewDynamicCredential(libs[1].GetPublicId(), credential.EgressPurpose), + NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose), + NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose), + NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose), } params.DynamicCredentials = creds return params diff --git a/internal/target/credential_library.go b/internal/target/credential_library.go index 97d83f811..8c013a4d8 100644 --- a/internal/target/credential_library.go +++ b/internal/target/credential_library.go @@ -21,7 +21,7 @@ type CredentialLibrary struct { // NewCredentialLibrary creates a new in memory CredentialLibrary // representing the relationship between targetId and credentialLibraryId. -func NewCredentialLibrary(targetId, credentialLibraryId string, _ ...Option) (*CredentialLibrary, error) { +func NewCredentialLibrary(targetId, credentialLibraryId string, purpose credential.Purpose) (*CredentialLibrary, error) { const op = "target.NewCredentialLibrary" if targetId == "" { return nil, errors.NewDeprecated(errors.InvalidParameter, op, "no target id") @@ -34,7 +34,7 @@ func NewCredentialLibrary(targetId, credentialLibraryId string, _ ...Option) (*C CredentialLibrary: &store.CredentialLibrary{ TargetId: targetId, CredentialLibraryId: credentialLibraryId, - CredentialPurpose: string(credential.ApplicationPurpose), // application is the only purpose currently supported + CredentialPurpose: string(purpose), }, } return t, nil diff --git a/internal/target/credential_library_test.go b/internal/target/credential_library_test.go index a079f471b..fb36b27f9 100644 --- a/internal/target/credential_library_test.go +++ b/internal/target/credential_library_test.go @@ -55,7 +55,7 @@ func TestCredentialLibrary_New(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) - got, err := target.NewCredentialLibrary(tt.args.targetId, tt.args.libraryId) + got, err := target.NewCredentialLibrary(tt.args.targetId, tt.args.libraryId, 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) diff --git a/internal/target/options.go b/internal/target/options.go index 5fa1bbe08..69565cd3c 100644 --- a/internal/target/options.go +++ b/internal/target/options.go @@ -30,7 +30,7 @@ type options struct { WithUserId string WithType subtypes.Subtype WithHostSources []string - WithCredentialSources []string + WithCredentialLibraries []*CredentialLibrary WithSessionMaxSeconds uint32 WithSessionConnectionLimit int32 WithPublicId string @@ -49,7 +49,7 @@ func getDefaultOptions() options { WithUserId: "", WithType: "", WithHostSources: nil, - WithCredentialSources: nil, + WithCredentialLibraries: nil, WithSessionMaxSeconds: uint32((8 * time.Hour).Seconds()), WithSessionConnectionLimit: 1, WithPublicId: "", @@ -129,10 +129,10 @@ func WithHostSources(hs []string) Option { } } -// WithCredentialSources provides an option for providing a list of credential source ids -func WithCredentialSources(cl []string) Option { +// WithCredentialLibraries provides an option for providing a list of credential libraries. +func WithCredentialLibraries(cl []*CredentialLibrary) Option { return func(o *options) { - o.WithCredentialSources = cl + o.WithCredentialLibraries = cl } } diff --git a/internal/target/options_test.go b/internal/target/options_test.go index a4d3431f5..0746102d6 100644 --- a/internal/target/options_test.go +++ b/internal/target/options_test.go @@ -3,6 +3,8 @@ package target import ( "testing" + "github.com/hashicorp/boundary/internal/credential" + "github.com/hashicorp/boundary/internal/target/store" "github.com/hashicorp/boundary/internal/types/subtypes" "github.com/stretchr/testify/assert" ) @@ -106,11 +108,37 @@ func Test_GetOpts(t *testing.T) { testOpts.WithWorkerFilter = `"/foo" == "bar"` assert.Equal(opts, testOpts) }) - t.Run("WithCredentialSources", func(t *testing.T) { + t.Run("WithCredentialLibraries", func(t *testing.T) { assert := assert.New(t) - opts := GetOpts(WithCredentialSources([]string{"alice", "bob"})) + opts := GetOpts(WithCredentialLibraries([]*CredentialLibrary{ + { + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: "alice", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + { + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: "bob", + CredentialPurpose: string(credential.EgressPurpose), + }, + }, + })) testOpts := getDefaultOptions() - testOpts.WithCredentialSources = []string{"alice", "bob"} + testOpts.WithCredentialLibraries = []*CredentialLibrary{ + { + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: "alice", + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + { + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: "bob", + CredentialPurpose: string(credential.EgressPurpose), + }, + }, + } assert.Equal(opts, testOpts) }) } diff --git a/internal/target/query.go b/internal/target/query.go index 7dc5c09a4..054861eee 100644 --- a/internal/target/query.go +++ b/internal/target/query.go @@ -13,7 +13,8 @@ current_libraries (library_id) as ( -- returns the current list select credential_library_id from target_credential_library - where target_id = @target_id + where target_id = @target_id + and credential_purpose = @purpose ), keep_libraries (library_id) as ( -- returns the KEEP list diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index 31284bf1e..2cad2a345 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -6,17 +6,46 @@ import ( "fmt" "strings" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/db" "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" ) +// 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, // 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, cIds []string, _ ...Option) (Target, []HostSource, []CredentialSource, error) { +func (r *Repository) AddTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...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") @@ -24,16 +53,12 @@ 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(cIds) == 0 { - return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential source ids") + if len(cls) == 0 { + return nil, nil, nil, errors.New(ctx, errors.InvalidParameter, op, "missing credential libraries") } - addCredLibs := make([]interface{}, 0, len(cIds)) - for _, id := range cIds { - cl, err := NewCredentialLibrary(targetId, id) - if err != nil { - return nil, nil, nil, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory credential library")) - } + addCredLibs := make([]interface{}, 0, len(cls)) + for _, cl := range cls { addCredLibs = append(addCredLibs, cl) } @@ -115,7 +140,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, csIds []string, _ ...Option) (int, error) { +func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...Option) (int, error) { const op = "target.(Repository).DeleteTargetCredentialSources" if targetId == "" { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target id") @@ -123,16 +148,12 @@ func (r *Repository) DeleteTargetCredentialSources(ctx context.Context, targetId if targetVersion == 0 { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") } - if len(csIds) == 0 { + if len(cls) == 0 { return db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing credential source ids") } - deleteCredLibs := make([]interface{}, 0, len(csIds)) - for _, id := range csIds { - cl, err := NewCredentialLibrary(targetId, id) - if err != nil { - return db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory credential library")) - } + deleteCredLibs := make([]interface{}, 0, len(cls)) + for _, cl := range cls { deleteCredLibs = append(deleteCredLibs, cl) } @@ -208,7 +229,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, csIds []string, _ ...Option) ([]HostSource, []CredentialSource, int, error) { +func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId string, targetVersion uint32, cls []*CredentialLibrary, _ ...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") @@ -217,10 +238,18 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st return nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing version") } - changes, err := r.changes(ctx, targetId, csIds) - if err != nil { - return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + clpm := newCredentialLibrariesPurposeMap(cls) + + var changes []*change + + for purpose, cls := range clpm { + c, err := r.changes(ctx, targetId, cls, purpose) + if err != nil { + return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op) + } + changes = append(changes, c...) } + if len(changes) == 0 { // Nothing needs to be changed, return early hostSets, err := fetchHostSources(ctx, r.reader, targetId) @@ -236,15 +265,11 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st var deleteCredLibs, addCredLibs []interface{} for _, c := range changes { - cl, err := NewCredentialLibrary(targetId, c.LibraryId) - if err != nil { - return nil, nil, db.NoRowsAffected, errors.Wrap(ctx, err, op, errors.WithMsg("unable to create in memory target credential library")) - } - switch c.Action { + switch c.action { case "delete": - deleteCredLibs = append(deleteCredLibs, cl) + deleteCredLibs = append(deleteCredLibs, c.credentialLibrary) case "add": - addCredLibs = append(addCredLibs, cl) + addCredLibs = append(addCredLibs, c.credentialLibrary) } } @@ -343,28 +368,33 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st } type change struct { + action string + credentialLibrary *CredentialLibrary +} + +type changeQueryResult struct { Action string LibraryId string } -func (r *Repository) changes(ctx context.Context, targetId string, clIds []string) ([]*change, error) { +func (r *Repository) changes(ctx context.Context, targetId string, cls []*CredentialLibrary, purpose string) ([]*change, error) { const op = "target.(Repository).changes" + + // TODO ensure that all cls have the same purpose as the given purpose? + var inClauseSpots []string - // starts at 2 because there is already a @1 in the query - for i := 2; i < len(clIds)+2; i++ { - inClauseSpots = append(inClauseSpots, fmt.Sprintf("@%d", i)) + 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)) + inClauseSpots = append(inClauseSpots, fmt.Sprintf("@%d", idx+1)) } inClause := strings.Join(inClauseSpots, ",") if inClause == "" { inClause = "''" } - query := fmt.Sprintf(setChangesQuery, inClause) - var params []interface{} - params = append(params, sql.Named("target_id", targetId)) - for idx, id := range clIds { - params = append(params, sql.Named(fmt.Sprintf("%d", idx+2), id)) - } + 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")) @@ -373,11 +403,20 @@ func (r *Repository) changes(ctx context.Context, targetId string, clIds []strin var changes []*change for rows.Next() { - var chg change + var chg changeQueryResult if err := r.reader.ScanRows(rows, &chg); err != nil { return nil, errors.Wrap(ctx, err, op, errors.WithMsg("scan row failed")) } - changes = append(changes, &chg) + changes = append(changes, &change{ + action: chg.Action, + credentialLibrary: &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + TargetId: targetId, + CredentialLibraryId: chg.LibraryId, + CredentialPurpose: purpose, + }, + }, + }) } return changes, nil } diff --git a/internal/target/repository_credential_source_test.go b/internal/target/repository_credential_source_test.go new file mode 100644 index 000000000..46cf8985c --- /dev/null +++ b/internal/target/repository_credential_source_test.go @@ -0,0 +1,254 @@ +package target_test + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/credential" + "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/targettest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepository_SetTargetCredentialSources(t *testing.T) { + target.Register(targettest.Subtype, targettest.Alloc, targettest.Vet, targettest.TargetPrefix) + + t.Parallel() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrapper := db.TestWrapper(t) + testKms := kms.TestKms(t, conn, wrapper) + repo, err := target.NewRepository(rw, rw, testKms) + require.NoError(t, err) + + 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) + 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)) + for _, cl := range credLibs { + cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) + } + + _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) + require.NoError(t, err) + require.Equal(t, 10, len(created)) + return created, cls + } + type args struct { + targetVersion uint32 + cls []*target.CredentialLibrary + addToOrigLibs bool + } + tests := []struct { + name string + setup func(target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) + args args + wantAffectedRows int + wantErr bool + wantErrCode errors.Code + }{ + { + name: "clear", + setup: setupFn, + args: args{ + targetVersion: 2, + cls: []*target.CredentialLibrary{}, + }, + wantErr: false, + wantAffectedRows: 10, + }, + { + name: "no-change", + setup: setupFn, + args: args{ + targetVersion: 2, + cls: []*target.CredentialLibrary{}, + addToOrigLibs: true, + }, + wantErr: false, + wantAffectedRows: 0, + }, + { + name: "add-cred-sources", + setup: setupFn, + args: args{ + targetVersion: 2, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, + addToOrigLibs: true, + }, + wantErr: false, + wantAffectedRows: 2, + }, + { + name: "add-multiple-purposes", + 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), + }, + addToOrigLibs: true, + }, + wantErr: false, + wantAffectedRows: 3, + }, + { + 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 + }, + args: args{ + targetVersion: 2, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.EgressPurpose), + }, + addToOrigLibs: false, + }, + 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), + }, + addToOrigLibs: true, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "bad version", + setup: setupFn, + args: args{ + targetVersion: 1000, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, + addToOrigLibs: true, + }, + wantErr: true, + wantErrCode: errors.VersionMismatch, + }, + { + name: "remove existing and add cred libs", + setup: setupFn, + args: args{ + targetVersion: 2, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, + addToOrigLibs: false, + }, + wantErr: false, + wantAffectedRows: 12, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + tar := targettest.TestNewTestTarget(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.CredentialLibrary) + for _, cl := range tt.args.cls { + cl.TargetId = tar.GetPublicId() + wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl + } + if tt.args.addToOrigLibs { + tt.args.cls = append(tt.args.cls, origCredLibraries...) + for _, cl := range origCredLibraries { + wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl + } + } + + origTarget, _, lookupCredSources, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) + require.NoError(err) + assert.Equal(origCredSources, lookupCredSources) + + _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, tt.args.cls) + if tt.wantErr { + require.Error(err) + assert.Equal(0, affectedRows) + assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) + return + } + t.Log(err) + require.NoError(err) + assert.Equal(tt.wantAffectedRows, affectedRows) + + assert.Equal(len(wantCredSources), len(got)) + + for _, cs := range got { + w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] + assert.True(ok, "got unexpected credentialsource %v", cs) + assert.Equal(w.CredentialLibraryId, cs.Id()) + assert.Equal(w.CredentialPurpose, string(cs.CredentialPurpose())) + } + + foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) + require.NoError(err) + if tt.name != "no-change" { + assert.Equalf(tt.args.targetVersion+1, foundTarget.GetVersion(), "%s unexpected version: %d/%d", tt.name, tt.args.targetVersion+1, foundTarget.GetVersion()) + assert.Equalf(origTarget.GetVersion(), foundTarget.GetVersion()-1, "%s unexpected version: %d/%d", tt.name, origTarget.GetVersion(), foundTarget.GetVersion()-1) + } + }) + } + 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}) + + require.Error(err) + assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error()) + }) + 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}) + + require.Error(err) + assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) + }) +} diff --git a/internal/target/targettest/store/target.pb.go b/internal/target/targettest/store/target.pb.go new file mode 100644 index 000000000..cb4e299fc --- /dev/null +++ b/internal/target/targettest/store/target.pb.go @@ -0,0 +1,307 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.27.1 +// protoc v3.17.3 +// source: controller/storage/target/targettest/store/v1/target.proto + +package store + +import ( + timestamp "github.com/hashicorp/boundary/internal/db/timestamp" + _ "github.com/hashicorp/boundary/sdk/pbs/controller/protooptions" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Target struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // public_id is used to access the targettest.Target via an API + // @inject_tag: gorm:"primary_key" + PublicId string `protobuf:"bytes,10,opt,name=public_id,json=publicId,proto3" json:"public_id,omitempty" gorm:"primary_key"` + // scope id for the targettest.Target + // @inject_tag: `gorm:"default:null"` + ScopeId string `protobuf:"bytes,20,opt,name=scope_id,json=scopeId,proto3" json:"scope_id,omitempty" gorm:"default:null"` + // name is the optional friendly name used to + // access the targettest.Target via an API + // @inject_tag: `gorm:"default:null"` + Name string `protobuf:"bytes,30,opt,name=name,proto3" json:"name,omitempty" gorm:"default:null"` + // description of the targettest.Target + // @inject_tag: `gorm:"default:null"` + Description string `protobuf:"bytes,40,opt,name=description,proto3" json:"description,omitempty" gorm:"default:null"` + // create_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + CreateTime *timestamp.Timestamp `protobuf:"bytes,50,opt,name=create_time,json=createTime,proto3" json:"create_time,omitempty" gorm:"default:current_timestamp"` + // update_time from the RDBMS + // @inject_tag: `gorm:"default:current_timestamp"` + UpdateTime *timestamp.Timestamp `protobuf:"bytes,60,opt,name=update_time,json=updateTime,proto3" json:"update_time,omitempty" gorm:"default:current_timestamp"` + // version allows optimistic locking of the targettest.Target when modifying the + // targettest.Target + // @inject_tag: `gorm:"default:null"` + Version uint32 `protobuf:"varint,70,opt,name=version,proto3" json:"version,omitempty" gorm:"default:null"` + // default port of the targettest.Target + // @inject_tag: `gorm:"default:null"` + DefaultPort uint32 `protobuf:"varint,80,opt,name=default_port,json=defaultPort,proto3" json:"default_port,omitempty" gorm:"default:null"` + // Maximum total lifetime of a created session, in seconds + // @inject_tag: `gorm:"default:null"` + SessionMaxSeconds uint32 `protobuf:"varint,100,opt,name=session_max_seconds,json=sessionMaxSeconds,proto3" json:"session_max_seconds,omitempty" gorm:"default:null"` + // Maximum number of connections in a session + // @inject_tag: `gorm:"default:null"` + SessionConnectionLimit int32 `protobuf:"varint,110,opt,name=session_connection_limit,json=sessionConnectionLimit,proto3" json:"session_connection_limit,omitempty" gorm:"default:null"` + // A boolean expression that allows filtering the workers that can handle a session + // @inject_tag: `gorm:"default:null"` + WorkerFilter string `protobuf:"bytes,120,opt,name=worker_filter,json=workerFilter,proto3" json:"worker_filter,omitempty" gorm:"default:null"` +} + +func (x *Target) Reset() { + *x = Target{} + if protoimpl.UnsafeEnabled { + mi := &file_controller_storage_target_targettest_store_v1_target_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Target) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Target) ProtoMessage() {} + +func (x *Target) ProtoReflect() protoreflect.Message { + mi := &file_controller_storage_target_targettest_store_v1_target_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Target.ProtoReflect.Descriptor instead. +func (*Target) Descriptor() ([]byte, []int) { + return file_controller_storage_target_targettest_store_v1_target_proto_rawDescGZIP(), []int{0} +} + +func (x *Target) GetPublicId() string { + if x != nil { + return x.PublicId + } + return "" +} + +func (x *Target) GetScopeId() string { + if x != nil { + return x.ScopeId + } + return "" +} + +func (x *Target) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Target) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Target) GetCreateTime() *timestamp.Timestamp { + if x != nil { + return x.CreateTime + } + return nil +} + +func (x *Target) GetUpdateTime() *timestamp.Timestamp { + if x != nil { + return x.UpdateTime + } + return nil +} + +func (x *Target) GetVersion() uint32 { + if x != nil { + return x.Version + } + return 0 +} + +func (x *Target) GetDefaultPort() uint32 { + if x != nil { + return x.DefaultPort + } + return 0 +} + +func (x *Target) GetSessionMaxSeconds() uint32 { + if x != nil { + return x.SessionMaxSeconds + } + return 0 +} + +func (x *Target) GetSessionConnectionLimit() int32 { + if x != nil { + return x.SessionConnectionLimit + } + return 0 +} + +func (x *Target) GetWorkerFilter() string { + if x != nil { + return x.WorkerFilter + } + return "" +} + +var File_controller_storage_target_targettest_store_v1_target_proto protoreflect.FileDescriptor + +var file_controller_storage_target_targettest_store_v1_target_proto_rawDesc = []byte{ + 0x0a, 0x3a, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, + 0x72, 0x61, 0x67, 0x65, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, 0x74, 0x61, 0x72, 0x67, + 0x65, 0x74, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x76, 0x31, 0x2f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2d, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, + 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2e, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x74, 0x65, + 0x73, 0x74, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2f, 0x76, 0x31, 0x2f, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2a, 0x63, 0x6f, + 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc3, 0x05, 0x0a, 0x06, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 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, 0x19, 0x0a, 0x08, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x14, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x09, 0x42, 0x10, 0xc2, 0xdd, 0x29, 0x0c, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x40, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x42, 0x1e, 0xc2, 0xdd, 0x29, 0x1a, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x4b, 0x0a, 0x0b, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, + 0x6d, 0x65, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x12, 0x4b, 0x0a, 0x0b, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, + 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x65, 0x72, 0x2e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x46, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x4d, 0x0a, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x50, 0x20, 0x01, 0x28, 0x0d, 0x42, 0x2a, 0xc2, + 0xdd, 0x29, 0x26, 0x0a, 0x0b, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, + 0x12, 0x17, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x66, + 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x0b, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x5c, 0x0a, 0x13, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x64, 0x20, + 0x01, 0x28, 0x0d, 0x42, 0x2c, 0xc2, 0xdd, 0x29, 0x28, 0x0a, 0x11, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x13, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x73, 0x52, 0x11, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x4d, 0x61, 0x78, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x70, 0x0a, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x6e, 0x20, 0x01, 0x28, 0x05, 0x42, 0x36, 0xc2, 0xdd, 0x29, 0x32, 0x0a, 0x16, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x12, 0x18, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x52, 0x16, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x46, 0x0a, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, + 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x78, 0x20, 0x01, 0x28, 0x09, 0x42, 0x21, 0xc2, + 0xdd, 0x29, 0x1d, 0x0a, 0x0c, 0x57, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, + 0x72, 0x12, 0x0d, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x52, 0x0c, 0x77, 0x6f, 0x72, 0x6b, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x42, 0x46, + 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x68, 0x61, 0x73, + 0x68, 0x69, 0x63, 0x6f, 0x72, 0x70, 0x2f, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x2f, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x2f, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x3b, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_controller_storage_target_targettest_store_v1_target_proto_rawDescOnce sync.Once + file_controller_storage_target_targettest_store_v1_target_proto_rawDescData = file_controller_storage_target_targettest_store_v1_target_proto_rawDesc +) + +func file_controller_storage_target_targettest_store_v1_target_proto_rawDescGZIP() []byte { + file_controller_storage_target_targettest_store_v1_target_proto_rawDescOnce.Do(func() { + file_controller_storage_target_targettest_store_v1_target_proto_rawDescData = protoimpl.X.CompressGZIP(file_controller_storage_target_targettest_store_v1_target_proto_rawDescData) + }) + return file_controller_storage_target_targettest_store_v1_target_proto_rawDescData +} + +var file_controller_storage_target_targettest_store_v1_target_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_controller_storage_target_targettest_store_v1_target_proto_goTypes = []interface{}{ + (*Target)(nil), // 0: controller.storage.target.targettest.store.v1.Target + (*timestamp.Timestamp)(nil), // 1: controller.storage.timestamp.v1.Timestamp +} +var file_controller_storage_target_targettest_store_v1_target_proto_depIdxs = []int32{ + 1, // 0: controller.storage.target.targettest.store.v1.Target.create_time:type_name -> controller.storage.timestamp.v1.Timestamp + 1, // 1: controller.storage.target.targettest.store.v1.Target.update_time:type_name -> controller.storage.timestamp.v1.Timestamp + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_controller_storage_target_targettest_store_v1_target_proto_init() } +func file_controller_storage_target_targettest_store_v1_target_proto_init() { + if File_controller_storage_target_targettest_store_v1_target_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_controller_storage_target_targettest_store_v1_target_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Target); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_controller_storage_target_targettest_store_v1_target_proto_rawDesc, + NumEnums: 0, + NumMessages: 1, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_controller_storage_target_targettest_store_v1_target_proto_goTypes, + DependencyIndexes: file_controller_storage_target_targettest_store_v1_target_proto_depIdxs, + MessageInfos: file_controller_storage_target_targettest_store_v1_target_proto_msgTypes, + }.Build() + File_controller_storage_target_targettest_store_v1_target_proto = out.File + file_controller_storage_target_targettest_store_v1_target_proto_rawDesc = nil + file_controller_storage_target_targettest_store_v1_target_proto_goTypes = nil + file_controller_storage_target_targettest_store_v1_target_proto_depIdxs = nil +} diff --git a/internal/target/targettest/target.go b/internal/target/targettest/target.go new file mode 100644 index 000000000..4109fa465 --- /dev/null +++ b/internal/target/targettest/target.go @@ -0,0 +1,262 @@ +// Package targettest provides a test target subtype for use by the target +// package. Note that it leverages the tcp.Target's database table to avoid +// needing schema migrations just for tests. +package targettest + +import ( + "context" + "testing" + + "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/errors" + "github.com/hashicorp/boundary/internal/oplog" + "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/targettest/store" + "github.com/hashicorp/boundary/internal/types/subtypes" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +const ( + Subtype = subtypes.Subtype("tcp") + TargetPrefix = "ttcp" +) + +// Target is a target.Target used for tests. +type Target struct { + *store.Target + tableName string `gorm:"-"` +} + +var ( + _ target.Target = (*Target)(nil) + _ db.VetForWriter = (*Target)(nil) + _ oplog.ReplayableMessage = (*Target)(nil) +) + +// VetForWrite implements db.VetForWrite() interface and validates the tcp target +// before it's written. +func (t *Target) VetForWrite(ctx context.Context, _ db.Reader, opType db.OpType, _ ...db.Option) error { + const op = "target_test.(Target).VetForWrite" + if t.PublicId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing public id") + } + if opType == db.CreateOp { + if t.ScopeId == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing scope id") + } + if t.Name == "" { + return errors.New(ctx, errors.InvalidParameter, op, "missing name") + } + } + return nil +} + +// TableName returns the tablename to override the default gorm table name +func (t *Target) TableName() string { + if t.tableName != "" { + return t.tableName + } + return "target_tcp" +} + +// SetTableName sets the tablename and satisfies the ReplayableMessage +// interface. If the caller attempts to set the name to "" the name will be +// reset to the default name. +func (t *Target) SetTableName(n string) { + t.tableName = n +} + +func (t *Target) GetPublicId() string { + return t.PublicId +} + +func (t *Target) GetScopeId() string { + return t.ScopeId +} + +func (t *Target) GetDefaultPort() uint32 { + return t.DefaultPort +} + +func (t *Target) GetName() string { + return t.Name +} + +func (t *Target) GetDescription() string { + return t.Description +} + +func (t *Target) GetVersion() uint32 { + return t.Version +} + +func (t *Target) GetType() subtypes.Subtype { + return Subtype +} + +func (t *Target) GetCreateTime() *timestamp.Timestamp { + return t.CreateTime +} + +func (t *Target) GetUpdateTime() *timestamp.Timestamp { + return t.UpdateTime +} + +func (t *Target) GetSessionMaxSeconds() uint32 { + return t.SessionMaxSeconds +} + +func (t *Target) GetSessionConnectionLimit() int32 { + return t.SessionConnectionLimit +} + +func (t *Target) GetWorkerFilter() string { + return t.WorkerFilter +} + +func (t *Target) Clone() target.Target { + cp := proto.Clone(t.Target) + return &Target{ + Target: cp.(*store.Target), + } +} + +func (t *Target) SetPublicId(_ context.Context, publicId string) error { + t.PublicId = publicId + return nil +} + +func (t *Target) SetScopeId(scopeId string) { + t.ScopeId = scopeId +} + +func (t *Target) SetName(name string) { + t.Name = name +} + +func (t *Target) SetDescription(description string) { + t.Description = description +} + +func (t *Target) SetVersion(v uint32) { + t.Version = v +} + +func (t *Target) SetDefaultPort(port uint32) { + t.DefaultPort = port +} + +func (t *Target) SetCreateTime(ts *timestamp.Timestamp) { + t.CreateTime = ts +} + +func (t *Target) SetUpdateTime(ts *timestamp.Timestamp) { + t.UpdateTime = ts +} + +func (t *Target) SetSessionMaxSeconds(s uint32) { + t.SessionMaxSeconds = s +} + +func (t *Target) SetSessionConnectionLimit(l int32) { + t.SessionConnectionLimit = l +} + +func (t *Target) SetWorkerFilter(f string) { + t.WorkerFilter = f +} + +func (t *Target) Oplog(op oplog.OpType) oplog.Metadata { + return oplog.Metadata{ + "resource-public-id": []string{t.PublicId}, + "resource-type": []string{"tcp target"}, + "op-type": []string{op.String()}, + "scope-id": []string{t.ScopeId}, + } +} + +// Alloc creates an in-memory Target. +func Alloc() target.Target { + return &Target{ + Target: &store.Target{}, + } +} + +// Vet checks that the given Target is a targettest.Target and that it is not nil. +func Vet(ctx context.Context, t target.Target) error { + const op = "targettest.Vet" + + tt, ok := t.(*Target) + if !ok { + return errors.New(ctx, errors.InvalidParameter, op, "target is not a tcp.Target") + } + + if tt == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing target") + } + + if tt.Target == nil { + return errors.New(ctx, errors.InvalidParameter, op, "missing target store") + } + return nil +} + +// New creates a targettest.Target. +func New(scopeId string, opt ...target.Option) (*Target, error) { + const op = "target_test.New" + opts := target.GetOpts(opt...) + if scopeId == "" { + return nil, errors.NewDeprecated(errors.InvalidParameter, op, "missing scope id") + } + t := &Target{ + Target: &store.Target{ + ScopeId: scopeId, + Name: opts.WithName, + Description: opts.WithDescription, + DefaultPort: opts.WithDefaultPort, + SessionConnectionLimit: opts.WithSessionConnectionLimit, + SessionMaxSeconds: opts.WithSessionMaxSeconds, + WorkerFilter: opts.WithWorkerFilter, + }, + } + return t, nil +} + +// TestNewTestTarget is a test helper for creating a targettest.Target. +func TestNewTestTarget(t *testing.T, conn *db.DB, scopeId, name string, opt ...target.Option) *Target { + t.Helper() + opt = append(opt, target.WithName(name)) + opts := target.GetOpts(opt...) + require := require.New(t) + rw := db.New(conn) + tar, err := New(scopeId, opt...) + require.NoError(err) + id, err := db.NewPublicId(TargetPrefix) + require.NoError(err) + tar.PublicId = id + err = rw.Create(context.Background(), tar) + require.NoError(err) + + if len(opts.WithHostSources) > 0 { + newHostSets := make([]interface{}, 0, len(opts.WithHostSources)) + for _, s := range opts.WithHostSources { + hostSet, err := target.NewTargetHostSet(tar.PublicId, s) + require.NoError(err) + newHostSets = append(newHostSets, hostSet) + } + err := rw.CreateItems(context.Background(), newHostSets) + require.NoError(err) + } + if len(opts.WithCredentialLibraries) > 0 { + newCredLibs := make([]interface{}, 0, len(opts.WithCredentialLibraries)) + for _, cl := range opts.WithCredentialLibraries { + cl.TargetId = tar.PublicId + newCredLibs = append(newCredLibs, cl) + } + err := rw.CreateItems(context.Background(), newCredLibs) + require.NoError(err) + } + return tar +} diff --git a/internal/target/tcp/repository_credential_source_test.go b/internal/target/tcp/repository_credential_source_test.go index ba0720773..7d1077c14 100644 --- a/internal/target/tcp/repository_credential_source_test.go +++ b/internal/target/tcp/repository_credential_source_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/errors" @@ -106,7 +107,12 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { assert, require := assert.New(t), require.New(t) projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, tt.name) - gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, tt.args.credLibIds) + credLibs := make([]*target.CredentialLibrary, 0, len(tt.args.credLibIds)) + for _, clid := range tt.args.credLibIds { + credLibs = append(credLibs, target.TestNewCredentialLibrary(projTarget.PublicId, clid, credential.ApplicationPurpose)) + } + + gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, credLibs) if tt.wantErr { require.Error(err) assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) @@ -142,18 +148,22 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { assert, require := assert.New(t), require.New(t) projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, "add-existing") - _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId}) + cl1 := target.TestNewCredentialLibrary(projTarget.PublicId, lib1.PublicId, credential.ApplicationPurpose) + cl2 := target.TestNewCredentialLibrary(projTarget.PublicId, lib2.PublicId, credential.ApplicationPurpose) + cl3 := target.TestNewCredentialLibrary(projTarget.PublicId, lib3.PublicId, credential.ApplicationPurpose) + + _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []*target.CredentialLibrary{cl1}) require.NoError(err) assert.Len(gotCredSources, 1) assert.Equal(lib1.PublicId, gotCredSources[0].Id()) // Adding lib1 again should error - _, _, _, err = repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []string{lib1.PublicId}) + _, _, _, err = repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []*target.CredentialLibrary{cl1}) require.Error(err) assert.True(errors.Match(errors.T(errors.NotUnique), err)) // Adding multiple with lib1 in set should error - _, _, _, err = repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []string{lib3.PublicId, lib2.PublicId, lib1.PublicId}) + _, _, _, err = repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []*target.CredentialLibrary{cl3, cl2, cl1}) require.Error(err) assert.True(errors.Match(errors.T(errors.NotUnique), err)) @@ -166,7 +176,8 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - _, _, _, err := repo.AddTargetCredentialSources(context.Background(), "fake-target-id", 1, []string{lib1.PublicId}) + cl1 := target.TestNewCredentialLibrary("fake-target-id", lib1.PublicId, credential.ApplicationPurpose) + _, _, _, err := repo.AddTargetCredentialSources(context.Background(), "fake-target-id", 1, []*target.CredentialLibrary{cl1}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.RecordNotFound), err), "unexpected error %s", err.Error()) @@ -275,29 +286,29 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { assert, require := assert.New(t), require.New(t) cs := css[i] - target := tcp.TestTarget(t, conn, proj.PublicId, tt.name) + tar := tcp.TestTarget(t, conn, proj.PublicId, tt.name) - clIds := make([]string, 0, tt.args.createCnt) + 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 { - clIds = append(clIds, cl.PublicId) + cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) } } - _, _, addedCredSources, err := repo.AddTargetCredentialSources(context.Background(), target.GetPublicId(), 1, clIds) + _, _, addedCredSources, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) require.NoError(err) assert.Equal(tt.args.createCnt, len(addedCredSources)) - deleteCredSources := make([]string, 0, tt.args.deleteCnt) + deleteCredSources := make([]*target.CredentialLibrary, 0, tt.args.deleteCnt) for i := 0; i < tt.args.deleteCnt; i++ { - deleteCredSources = append(deleteCredSources, clIds[i]) + deleteCredSources = append(deleteCredSources, cls[i]) } var targetId string switch { case tt.args.targetIdOverride != nil: targetId = *tt.args.targetIdOverride default: - targetId = target.GetPublicId() + targetId = tar.GetPublicId() } var targetVersion uint32 switch { @@ -317,7 +328,7 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { assert.Equal(tt.wantRowsDeleted, deletedRows) // we should find the oplog for the delete of target credential libraries - err = db.TestVerifyOplog(t, rw, target.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) + err = db.TestVerifyOplog(t, rw, tar.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second)) assert.NoError(err) }) } @@ -333,18 +344,22 @@ func TestRepository_DeleteTargetCredentialSources(t *testing.T) { lib3 := libs[2] projTarget := tcp.TestTarget(t, conn, proj.PublicId, "add-existing") - _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []string{lib1.PublicId, lib2.PublicId}) + cl1 := target.TestNewCredentialLibrary(projTarget.PublicId, lib1.PublicId, credential.ApplicationPurpose) + cl2 := target.TestNewCredentialLibrary(projTarget.PublicId, lib2.PublicId, credential.ApplicationPurpose) + cl3 := target.TestNewCredentialLibrary(projTarget.PublicId, lib3.PublicId, credential.ApplicationPurpose) + + _, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, 1, []*target.CredentialLibrary{cl1, cl2}) require.NoError(err) assert.Len(gotCredSources, 2) // Deleting an unassociated source should return an error - delCount, err := repo.DeleteTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []string{lib3.PublicId}) + delCount, err := repo.DeleteTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []*target.CredentialLibrary{cl3}) 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(context.Background(), projTarget.PublicId, 2, []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}) + delCount, err = repo.DeleteTargetCredentialSources(context.Background(), projTarget.PublicId, 2, []*target.CredentialLibrary{cl1, cl2, cl3}) require.Error(err) assert.True(errors.Match(errors.T(errors.MultipleRecords), err)) assert.Equal(0, delCount) @@ -373,17 +388,17 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { lib1 := credLibs[0] lib2 := credLibs[1] - setupFn := func(target target.Target) []target.CredentialSource { + setupFn := func(tar target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) { credLibs := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 10) - clIds := make([]string, 0, len(credLibs)) + cls := make([]*target.CredentialLibrary, 0, len(credLibs)) for _, cl := range credLibs { - clIds = append(clIds, cl.PublicId) + cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), cl.PublicId, credential.ApplicationPurpose)) } - _, _, created, err := repo.AddTargetCredentialSources(context.Background(), target.GetPublicId(), 1, clIds) + _, _, created, err := repo.AddTargetCredentialSources(context.Background(), tar.GetPublicId(), 1, cls) require.NoError(t, err) require.Equal(t, 10, len(created)) - return created + return created, cls } type args struct { targetVersion uint32 @@ -392,7 +407,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { } tests := []struct { name string - setup func(target.Target) []target.CredentialSource + setup func(target.Target) ([]target.CredentialSource, []*target.CredentialLibrary) args args wantAffectedRows int wantErr bool @@ -471,22 +486,29 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { tar := tcp.TestTarget(t, conn, proj.PublicId, tt.name) var origCredSources []target.CredentialSource + var origCredLibraries []*target.CredentialLibrary if tt.setup != nil { - origCredSources = tt.setup(tar) + origCredSources, origCredLibraries = tt.setup(tar) } + + cls := make([]*target.CredentialLibrary, 0, len(tt.args.clIds)) + for _, clid := range tt.args.clIds { + cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), clid, credential.ApplicationPurpose)) + } + var wantIds []string + wantIds = append(wantIds, tt.args.clIds...) if tt.args.addToOrigLibs { - origIds := make([]string, 0, len(origCredSources)) - for _, cl := range origCredSources { - origIds = append(origIds, cl.Id()) + cls = append(cls, origCredLibraries...) + for _, cl := range origCredLibraries { + wantIds = append(wantIds, cl.CredentialLibraryId) } - tt.args.clIds = append(tt.args.clIds, origIds...) } origTarget, _, lookupCredSources, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) require.NoError(err) assert.Equal(origCredSources, lookupCredSources) - _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, tt.args.clIds) + _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, cls) if tt.wantErr { require.Error(err) assert.Equal(0, affectedRows) @@ -496,10 +518,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Log(err) require.NoError(err) assert.Equal(tt.wantAffectedRows, affectedRows) - assert.Equal(len(tt.args.clIds), len(got)) - var wantIds []string - wantIds = append(wantIds, tt.args.clIds...) + assert.Equal(len(wantIds), len(got)) sort.Strings(wantIds) var gotIds []string @@ -523,7 +543,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("missing-target-id", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, []string{lib1.PublicId}) + cl1 := target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "", 1, []*target.CredentialLibrary{cl1}) require.Error(err) assert.Truef(errors.Match(errors.T(errors.InvalidParameter), err), "unexpected error %s", err.Error()) @@ -531,7 +552,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { t.Run("target-not-found", func(t *testing.T) { assert, require := assert.New(t), require.New(t) - _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, []string{lib1.PublicId}) + cl1 := target.TestNewCredentialLibrary("fake-target-id", lib1.PublicId, credential.ApplicationPurpose) + _, _, _, err := repo.SetTargetCredentialSources(context.Background(), "fake-target-id", 1, []*target.CredentialLibrary{cl1}) 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 6ae271b5c..9b2338a67 100644 --- a/internal/target/tcp/repository_tcp_target_test.go +++ b/internal/target/tcp/repository_tcp_target_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" @@ -14,6 +15,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,9 +41,16 @@ func TestRepository_CreateTarget(t *testing.T) { cs := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] credSources := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 2) - var clIds []string + var credLibraries []*target.CredentialLibrary + var credLibIds []string for _, cl := range credSources { - clIds = append(clIds, cl.PublicId) + credLibraries = append(credLibraries, &target.CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: cl.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }) + credLibIds = append(credLibIds, cl.PublicId) } type args struct { @@ -392,11 +401,22 @@ func TestRepository_UpdateTcpTarget(t *testing.T) { cls := vault.TestCredentialLibraries(t, conn, wrapper, cs.GetPublicId(), 5) var testClIds []string + var testCredLibs []*target.CredentialLibrary for _, cl := range cls { + testCredLibs = append(testCredLibs, &target.CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: cl.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }) testClIds = append(testClIds, cl.PublicId) } - tt.newTargetOpts = append(tt.newTargetOpts, target.WithHostSources(testHostSetIds), target.WithCredentialSources(testClIds)) + tt.newTargetOpts = append( + tt.newTargetOpts, + target.WithHostSources(testHostSetIds), + target.WithCredentialLibraries(testCredLibs), + ) name := tt.newName if name == "" { name = tcp.TestId(t) diff --git a/internal/target/tcp/testing.go b/internal/target/tcp/testing.go index f78f61e9b..6e917abd4 100644 --- a/internal/target/tcp/testing.go +++ b/internal/target/tcp/testing.go @@ -36,12 +36,11 @@ func TestTarget(t *testing.T, conn *db.DB, scopeId, name string, opt ...target.O err := rw.CreateItems(context.Background(), newHostSets) require.NoError(err) } - if len(opts.WithCredentialSources) > 0 { - newCredLibs := make([]interface{}, 0, len(opts.WithCredentialSources)) - for _, cl := range opts.WithCredentialSources { - newCl, err := target.NewCredentialLibrary(tar.PublicId, cl) - require.NoError(err) - newCredLibs = append(newCredLibs, newCl) + if len(opts.WithCredentialLibraries) > 0 { + newCredLibs := make([]interface{}, 0, len(opts.WithCredentialLibraries)) + for _, cl := range opts.WithCredentialLibraries { + cl.TargetId = tar.PublicId + newCredLibs = append(newCredLibs, cl) } err := rw.CreateItems(context.Background(), newCredLibs) require.NoError(err) diff --git a/internal/target/testing.go b/internal/target/testing.go index b436a7963..9460fd382 100644 --- a/internal/target/testing.go +++ b/internal/target/testing.go @@ -4,19 +4,33 @@ import ( "context" "testing" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/target/store" "github.com/stretchr/testify/require" ) +// TestNewCredentialLibrary creates a new in memory CredentialLibrary +// representing the relationship between targetId and credentialLibraryId with +// the given purpose. +func TestNewCredentialLibrary(targetId, credentialLibraryId string, purpose credential.Purpose) *CredentialLibrary { + return &CredentialLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + TargetId: targetId, + CredentialLibraryId: credentialLibraryId, + CredentialPurpose: string(purpose), + }, + } +} + // TestCredentialLibrary creates a CredentialLibrary for targetId and -// libraryId. +// libraryId with the credential purpose of application. func TestCredentialLibrary(t *testing.T, conn *db.DB, targetId, libraryId string) *CredentialLibrary { t.Helper() require := require.New(t) rw := db.New(conn) - lib, err := NewCredentialLibrary(targetId, libraryId) - require.NoError(err) - err = rw.Create(context.Background(), lib) + lib := TestNewCredentialLibrary(targetId, libraryId, credential.ApplicationPurpose) + err := rw.Create(context.Background(), lib) require.NoError(err) return lib } From 1b7cfb170450d1454f699ba2cb713dd88c0eb0e4 Mon Sep 17 00:00:00 2001 From: Timothy Messier Date: Tue, 12 Oct 2021 10:53:33 -0400 Subject: [PATCH 2/2] feat(target): Add support for credential library validation Target subtypes must now implement another repository hook to validate any CredentialLibraries that are being added/set. This allows for subtypes to determine the valid set of CredentialLibraries. For tcp.Targets only CredentialLibaries with credential.ApplicationPurpose are allowed. If any other purpose is provided an error is returned. --- .../migrations/oss/postgres_16_05_test.go | 6 +- internal/session/repository_session_test.go | 6 +- internal/target/registry.go | 28 ++- .../target/repository_credential_source.go | 17 ++ .../repository_credential_source_test.go | 16 +- internal/target/targettest/target.go | 5 + internal/target/tcp/register.go | 17 +- .../tcp/repository_credential_source_test.go | 208 +++++++++++++----- 8 files changed, 227 insertions(+), 76 deletions(-) diff --git a/internal/db/schema/migrations/oss/postgres_16_05_test.go b/internal/db/schema/migrations/oss/postgres_16_05_test.go index b63444085..a6c4bc081 100644 --- a/internal/db/schema/migrations/oss/postgres_16_05_test.go +++ b/internal/db/schema/migrations/oss/postgres_16_05_test.go @@ -168,8 +168,7 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrappe libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) libs := []*target.CredentialLibrary{ target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.ApplicationPurpose), } targetRepo, err := target.NewRepository(rw, rw, kms) @@ -178,8 +177,7 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, kms *kms.Kms, wrappe require.NoError(t, err) creds := []*session.DynamicCredential{ session.NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose), - session.NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose), - session.NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose), + session.NewDynamicCredential(libIds[1].GetPublicId(), credential.ApplicationPurpose), } return creds } diff --git a/internal/session/repository_session_test.go b/internal/session/repository_session_test.go index f2107a71d..1bc59ee9d 100644 --- a/internal/session/repository_session_test.go +++ b/internal/session/repository_session_test.go @@ -1661,16 +1661,14 @@ func testSessionCredentialParams(t *testing.T, conn *db.DB, wrapper wrapping.Wra libIds := vault.TestCredentialLibraries(t, conn, wrapper, stores[0].GetPublicId(), 2) libs := []*target.CredentialLibrary{ target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.ApplicationPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[0].GetPublicId(), credential.IngressPurpose), - target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.EgressPurpose), + target.TestNewCredentialLibrary(tar.GetPublicId(), libIds[1].GetPublicId(), credential.ApplicationPurpose), } _, _, _, err = targetRepo.AddTargetCredentialSources(ctx, tar.GetPublicId(), tar.GetVersion(), libs) require.NoError(err) creds := []*DynamicCredential{ NewDynamicCredential(libIds[0].GetPublicId(), credential.ApplicationPurpose), - NewDynamicCredential(libIds[0].GetPublicId(), credential.IngressPurpose), - NewDynamicCredential(libIds[1].GetPublicId(), credential.EgressPurpose), + NewDynamicCredential(libIds[1].GetPublicId(), credential.ApplicationPurpose), } params.DynamicCredentials = creds return params diff --git a/internal/target/registry.go b/internal/target/registry.go index d33f3035c..333cdd725 100644 --- a/internal/target/registry.go +++ b/internal/target/registry.go @@ -15,10 +15,15 @@ type AllocFunc func() Target // be used by the Repository. type VetFunc func(context.Context, Target) 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 + type registryEntry struct { - alloc AllocFunc - vet VetFunc - prefix string + alloc AllocFunc + vet VetFunc + vetCredentialLibraries VetCredentialLibrariesFunc + prefix string } type registry struct { @@ -71,6 +76,14 @@ func (r *registry) vetFunc(s subtypes.Subtype) (VetFunc, bool) { return entry.vet, ok } +func (r *registry) vetCredentialLibrariesFunc(s subtypes.Subtype) (VetCredentialLibrariesFunc, bool) { + entry, ok := r.get(s) + if !ok { + return nil, ok + } + return entry.vetCredentialLibraries, ok +} + func (r *registry) idPrefix(s subtypes.Subtype) (string, bool) { entry, ok := r.get(s) if !ok { @@ -103,10 +116,11 @@ func SubtypeFromId(id string) subtypes.Subtype { // Register registers repository hooks and the prefixes for a provided Subtype. Register // panics if the subtype has already been registered or if any of the // prefixes are associated with another subtype. -func Register(s subtypes.Subtype, af AllocFunc, vf VetFunc, prefix string) { +func Register(s subtypes.Subtype, af AllocFunc, vf VetFunc, vclf VetCredentialLibrariesFunc, prefix string) { subtypeRegistry.set(s, ®istryEntry{ - alloc: af, - vet: vf, - prefix: prefix, + alloc: af, + vet: vf, + vetCredentialLibraries: vclf, + prefix: prefix, }) } diff --git a/internal/target/repository_credential_source.go b/internal/target/repository_credential_source.go index 2cad2a345..8d158b885 100644 --- a/internal/target/repository_credential_source.go +++ b/internal/target/repository_credential_source.go @@ -74,6 +74,14 @@ 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 + } + target := alloc() target.SetPublicId(ctx, t.PublicId) target.SetVersion(targetVersion + 1) @@ -284,6 +292,15 @@ func (r *Repository) SetTargetCredentialSources(ctx context.Context, targetId st 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)) } + + vetCredentialLibraries, ok := subtypeRegistry.vetCredentialLibrariesFunc(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 { + return nil, nil, db.NoRowsAffected, err + } + target := alloc() target.SetPublicId(ctx, t.PublicId) target.SetVersion(targetVersion + 1) diff --git a/internal/target/repository_credential_source_test.go b/internal/target/repository_credential_source_test.go index 46cf8985c..d03c438f8 100644 --- a/internal/target/repository_credential_source_test.go +++ b/internal/target/repository_credential_source_test.go @@ -17,7 +17,7 @@ import ( ) func TestRepository_SetTargetCredentialSources(t *testing.T) { - target.Register(targettest.Subtype, targettest.Alloc, targettest.Vet, targettest.TargetPrefix) + target.Register(targettest.Subtype, targettest.Alloc, targettest.Vet, targettest.VetCredentialLibraries, targettest.TargetPrefix) t.Parallel() conn, _ := db.TestSetup(t, "postgres") @@ -189,15 +189,19 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { origCredSources, origCredLibraries = tt.setup(tar) } - wantCredSources := make(map[string]*target.CredentialLibrary) + wantCredSources := make(map[string]target.CredentialSource) for _, cl := range tt.args.cls { cl.TargetId = tar.GetPublicId() - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl + wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ + CredentialLibrary: cl.CredentialLibrary, + } } if tt.args.addToOrigLibs { tt.args.cls = append(tt.args.cls, origCredLibraries...) for _, cl := range origCredLibraries { - wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = cl + wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ + CredentialLibrary: cl.CredentialLibrary, + } } } @@ -221,8 +225,8 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { for _, cs := range got { w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] assert.True(ok, "got unexpected credentialsource %v", cs) - assert.Equal(w.CredentialLibraryId, cs.Id()) - assert.Equal(w.CredentialPurpose, string(cs.CredentialPurpose())) + assert.Equal(w.Id(), cs.Id()) + assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose()) } foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) diff --git a/internal/target/targettest/target.go b/internal/target/targettest/target.go index 4109fa465..4d9b3e31b 100644 --- a/internal/target/targettest/target.go +++ b/internal/target/targettest/target.go @@ -203,6 +203,11 @@ func Vet(ctx context.Context, t target.Target) error { return nil } +// VetCredentialLibraries allows for any CredentialLibraries. +func VetCredentialLibraries(_ context.Context, _ []*target.CredentialLibrary) error { + return nil +} + // New creates a targettest.Target. func New(scopeId string, opt ...target.Option) (*Target, error) { const op = "target_test.New" diff --git a/internal/target/tcp/register.go b/internal/target/tcp/register.go index 68508d430..943ff8ac8 100644 --- a/internal/target/tcp/register.go +++ b/internal/target/tcp/register.go @@ -2,13 +2,15 @@ package tcp import ( "context" + "fmt" + "github.com/hashicorp/boundary/internal/credential" "github.com/hashicorp/boundary/internal/errors" "github.com/hashicorp/boundary/internal/target" ) func init() { - target.Register(Subtype, allocTarget, vet, TargetPrefix) + target.Register(Subtype, allocTarget, vet, vetCredentialLibraries, TargetPrefix) } const ( @@ -35,3 +37,16 @@ func vet(ctx context.Context, t target.Target) error { } return nil } + +// vetCredentialLibraries checks that all of the provided credential libriaries have a CredentialPurpose +// of ApplicationPurpose. Any other CredentialPurpose will result in an error. +func vetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error { + const op = "tcp.vetCredentialLibraries" + + for _, cl := range cls { + if cl.CredentialPurpose != string(credential.ApplicationPurpose) { + return errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("tcp.Target only supports credential purpose: %q", credential.ApplicationPurpose)) + } + } + return nil +} diff --git a/internal/target/tcp/repository_credential_source_test.go b/internal/target/tcp/repository_credential_source_test.go index 7d1077c14..1b0c80b28 100644 --- a/internal/target/tcp/repository_credential_source_test.go +++ b/internal/target/tcp/repository_credential_source_test.go @@ -2,7 +2,6 @@ package tcp_test import ( "context" - "sort" "testing" "time" @@ -14,6 +13,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/oplog" "github.com/hashicorp/boundary/internal/target" + "github.com/hashicorp/boundary/internal/target/store" "github.com/hashicorp/boundary/internal/target/tcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,14 +40,14 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { type args struct { targetVersion uint32 - credLibIds []string + cls []*target.CredentialLibrary } tests := []struct { - name string - args args - wantCredLibIds []string - wantErr bool - wantErrCode errors.Code + name string + args args + wantCredSources map[string]target.CredentialSource + wantErr bool + wantErrCode errors.Code }{ { name: "zero-version", @@ -69,34 +69,95 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { name: "valid-single-source", args: args{ targetVersion: 1, - credLibIds: []string{lib1.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + }, }, - wantCredLibIds: []string{lib1.PublicId}, - wantErr: false, + wantCredSources: map[string]target.CredentialSource{ + lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: lib1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + wantErr: false, }, { name: "valid-multiple-sources", args: args{ targetVersion: 1, - credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib3.PublicId, credential.ApplicationPurpose), + }, }, - wantCredLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId}, - wantErr: false, + wantCredSources: map[string]target.CredentialSource{ + lib1.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: lib1.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + lib2.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: lib2.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + lib3.PublicId + "_" + string(credential.ApplicationPurpose): &target.TargetLibrary{ + CredentialLibrary: &store.CredentialLibrary{ + CredentialLibraryId: lib3.PublicId, + CredentialPurpose: string(credential.ApplicationPurpose), + }, + }, + }, + wantErr: false, }, { name: "invalid-source-id", args: args{ targetVersion: 1, - credLibIds: []string{lib1.PublicId, lib2.PublicId, lib3.PublicId, "invalid-source-id"}, + 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), + }, }, wantErr: true, wantErrCode: errors.NotSpecificIntegrity, }, + { + name: "egress-credential-purpose", + args: args{ + targetVersion: 1, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "ingress-credential-purpose", + args: args{ + targetVersion: 1, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose), + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, { name: "bad-version", args: args{ targetVersion: 1000, - credLibIds: []string{lib1.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + }, }, wantErr: true, wantErrCode: errors.VersionMismatch, @@ -107,25 +168,25 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { assert, require := assert.New(t), require.New(t) projTarget := tcp.TestTarget(t, conn, staticProj.PublicId, tt.name) - credLibs := make([]*target.CredentialLibrary, 0, len(tt.args.credLibIds)) - for _, clid := range tt.args.credLibIds { - credLibs = append(credLibs, target.TestNewCredentialLibrary(projTarget.PublicId, clid, credential.ApplicationPurpose)) + + for _, cl := range tt.args.cls { + cl.TargetId = projTarget.PublicId } - gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, credLibs) + gotTarget, _, gotCredSources, err := repo.AddTargetCredentialSources(context.Background(), projTarget.PublicId, tt.args.targetVersion, tt.args.cls) if tt.wantErr { require.Error(err) assert.Truef(errors.Match(errors.T(tt.wantErrCode), err), "unexpected error %s", err.Error()) return } require.NoError(err) - assert.Len(gotCredSources, len(tt.wantCredLibIds)) - gotCredSourcesMap := map[string]target.CredentialSource{} - for _, s := range gotCredSources { - gotCredSourcesMap[s.Id()] = s - } - for _, id := range tt.wantCredLibIds { - assert.NotEmpty(gotCredSourcesMap[id]) + assert.Len(gotCredSources, len(tt.wantCredSources)) + + for _, cs := range gotCredSources { + w, ok := tt.wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] + assert.True(ok, "got unexpected credentialsource %v", cs) + assert.Equal(w.Id(), cs.Id()) + assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose()) } // test to see of the target version update oplog was created @@ -138,9 +199,13 @@ func TestRepository_AddTargetCredentialSources(t *testing.T) { assert.Equal(projTarget.GetVersion(), tar.GetVersion()-1) assert.True(proto.Equal(gotTarget.(*tcp.Target), tar.(*tcp.Target))) assert.Equal(gotCredSources, lookupCredSources) - for _, s := range lookupCredSources { - assert.NotEmpty(gotCredSourcesMap[s.Id()]) - assert.Equal(projTarget.PublicId, s.TargetId()) + + for _, cs := range lookupCredSources { + w, ok := tt.wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] + assert.True(ok, "got unexpected credentialsource %v", cs) + assert.Equal(w.Id(), cs.Id()) + assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose()) + assert.Equal(projTarget.PublicId, cs.TargetId()) } }) } @@ -402,7 +467,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { } type args struct { targetVersion uint32 - clIds []string + cls []*target.CredentialLibrary addToOrigLibs bool } tests := []struct { @@ -418,7 +483,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - clIds: []string{}, + cls: []*target.CredentialLibrary{}, }, wantErr: false, wantAffectedRows: 10, @@ -428,7 +493,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - clIds: []string{}, + cls: []*target.CredentialLibrary{}, addToOrigLibs: true, }, wantErr: false, @@ -439,7 +504,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - clIds: []string{lib1.PublicId, lib2.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, addToOrigLibs: true, }, wantErr: false, @@ -450,7 +518,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 0, - clIds: []string{lib1.PublicId, lib2.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, addToOrigLibs: true, }, wantErr: true, @@ -461,7 +532,10 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 1000, - clIds: []string{lib1.PublicId, lib2.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, addToOrigLibs: true, }, wantErr: true, @@ -472,12 +546,39 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { setup: setupFn, args: args{ targetVersion: 2, - clIds: []string{lib1.PublicId, lib2.PublicId}, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.ApplicationPurpose), + target.TestNewCredentialLibrary("", lib2.PublicId, credential.ApplicationPurpose), + }, addToOrigLibs: false, }, wantErr: false, wantAffectedRows: 12, }, + { + name: "egress-credential-purpose", + setup: setupFn, + args: args{ + targetVersion: 1, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.EgressPurpose), + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, + { + name: "ingress-credential-purpose", + setup: setupFn, + args: args{ + targetVersion: 1, + cls: []*target.CredentialLibrary{ + target.TestNewCredentialLibrary("", lib1.PublicId, credential.IngressPurpose), + }, + }, + wantErr: true, + wantErrCode: errors.InvalidParameter, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -491,16 +592,19 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { origCredSources, origCredLibraries = tt.setup(tar) } - cls := make([]*target.CredentialLibrary, 0, len(tt.args.clIds)) - for _, clid := range tt.args.clIds { - cls = append(cls, target.TestNewCredentialLibrary(tar.GetPublicId(), clid, credential.ApplicationPurpose)) + 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, + } } - var wantIds []string - wantIds = append(wantIds, tt.args.clIds...) if tt.args.addToOrigLibs { - cls = append(cls, origCredLibraries...) + tt.args.cls = append(tt.args.cls, origCredLibraries...) for _, cl := range origCredLibraries { - wantIds = append(wantIds, cl.CredentialLibraryId) + wantCredSources[cl.CredentialLibraryId+"_"+cl.CredentialPurpose] = &target.TargetLibrary{ + CredentialLibrary: cl.CredentialLibrary, + } } } @@ -508,7 +612,7 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(origCredSources, lookupCredSources) - _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, cls) + _, got, affectedRows, err := repo.SetTargetCredentialSources(context.Background(), tar.GetPublicId(), tt.args.targetVersion, tt.args.cls) if tt.wantErr { require.Error(err) assert.Equal(0, affectedRows) @@ -519,18 +623,14 @@ func TestRepository_SetTargetCredentialSources(t *testing.T) { require.NoError(err) assert.Equal(tt.wantAffectedRows, affectedRows) - assert.Equal(len(wantIds), len(got)) - sort.Strings(wantIds) + assert.Equal(len(wantCredSources), len(got)) - var gotIds []string - if len(got) > 0 { - gotIds = make([]string, 0, len(got)) - for _, s := range got { - gotIds = append(gotIds, s.Id()) - } + for _, cs := range got { + w, ok := wantCredSources[cs.Id()+"_"+string(cs.CredentialPurpose())] + assert.True(ok, "got unexpected credentialsource %v", cs) + assert.Equal(w.Id(), cs.Id()) + assert.Equal(w.CredentialPurpose(), cs.CredentialPurpose()) } - sort.Strings(gotIds) - assert.Equal(wantIds, gotIds) foundTarget, _, _, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) require.NoError(err)