// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package tcp_test import ( "context" "crypto/x509" "encoding/base64" "encoding/json" "encoding/pem" "errors" "fmt" "path" "slices" "strings" "sync/atomic" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/hashicorp/boundary/globals" talias "github.com/hashicorp/boundary/internal/alias/target" "github.com/hashicorp/boundary/internal/auth/ldap" "github.com/hashicorp/boundary/internal/auth/oidc" "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/credential" credstatic "github.com/hashicorp/boundary/internal/credential/static" "github.com/hashicorp/boundary/internal/credential/vault" "github.com/hashicorp/boundary/internal/daemon/controller/auth" "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentiallibraries" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/credentials" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets" "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/event" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" authpb "github.com/hashicorp/boundary/internal/gen/controller/auth" hostplugin "github.com/hashicorp/boundary/internal/host/plugin" "github.com/hashicorp/boundary/internal/host/static" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/boundary/internal/plugin" "github.com/hashicorp/boundary/internal/plugin/loopback" "github.com/hashicorp/boundary/internal/requests" "github.com/hashicorp/boundary/internal/scheduler" "github.com/hashicorp/boundary/internal/server" "github.com/hashicorp/boundary/internal/session" "github.com/hashicorp/boundary/internal/target" "github.com/hashicorp/boundary/internal/target/tcp" "github.com/hashicorp/boundary/internal/target/tcp/store" "github.com/hashicorp/boundary/internal/types/scope" credlibpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentiallibraries" credpb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/credentials" "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes" pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets" plgpb "github.com/hashicorp/boundary/sdk/pbs/plugin" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh/testdata" "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/structpb" "google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/wrapperspb" _ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets/tcp" ) var testAuthorizedActions = []string{ "no-op", "read", "update", "delete", "add-host-sources", "set-host-sources", "remove-host-sources", "add-credential-sources", "set-credential-sources", "remove-credential-sources", "authorize-session", } // Create a variable that we can overwrite in enterprise tests var expectedDeprecatedWorkerFilterError = "Use egress_worker_filter instead" func testService(t *testing.T, ctx context.Context, conn *db.DB, kms *kms.Kms, wrapper wrapping.Wrapper) (targets.Service, error) { rw := db.New(conn) sche := scheduler.TestScheduler(t, conn, wrapper) workerRPCGracePeriod := new(atomic.Int64) workerRPCGracePeriod.Store(int64(server.DefaultLiveness)) repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms, o...) } iamRepoFn := func() (*iam.Repository, error) { return iam.TestRepo(t, conn, wrapper), nil } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) { return session.NewRepository(ctx, rw, rw, kms, opts...) } staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(ctx, rw, rw, kms) } pluginHostRepoFn := func() (*hostplugin.Repository, error) { return hostplugin.NewRepository(ctx, rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) } vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } staticCredRepoFn := func() (*credstatic.Repository, error) { return credstatic.NewRepository(context.Background(), rw, rw, kms) } targetAliasRepoFn := func() (*talias.Repository, error) { return talias.NewRepository(ctx, rw, rw, kms) } return targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, workerRPCGracePeriod, 1000, nil) } func TestGet(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } o, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})) tarAddr := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test address", target.WithAddress("8.8.8.8")) al := talias.TestAlias(t, rw, "test.alias", talias.WithDestinationId(tar.GetPublicId()), talias.WithHostId("hsht_1234567890")) pAlias := &pb.Alias{ Id: al.GetPublicId(), Value: al.GetValue(), } pTar := &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj.GetPublicId(), Name: wrapperspb.String("test"), CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: o.GetPublicId()}, Type: tcp.Subtype.String(), HostSourceIds: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, Aliases: []*pb.Alias{pAlias}, } for _, ihs := range hs { pTar.HostSources = append(pTar.HostSources, &pb.HostSource{Id: ihs.GetPublicId(), HostCatalogId: ihs.GetCatalogId()}) } pTarAddr := &pb.Target{ Id: tarAddr.GetPublicId(), ScopeId: proj.GetPublicId(), Name: wrapperspb.String("test address"), CreatedTime: tarAddr.GetCreateTime().GetTimestamp(), UpdatedTime: tarAddr.GetUpdateTime().GetTimestamp(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: o.GetPublicId()}, Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{Value: "8.8.8.8"}, } cases := []struct { name string req *pbs.GetTargetRequest res *pbs.GetTargetResponse err error }{ { name: "Get an Existing Target", req: &pbs.GetTargetRequest{Id: tar.GetPublicId()}, res: &pbs.GetTargetResponse{Item: pTar}, }, { name: "Get an Existing Target w/ address", req: &pbs.GetTargetRequest{Id: tarAddr.GetPublicId()}, res: &pbs.GetTargetResponse{Item: pTarAddr}, }, { name: "Get a non existing Target", req: &pbs.GetTargetRequest{Id: globals.TcpTargetPrefix + "_DoesntExis"}, res: nil, err: handlers.ApiErrorWithCode(codes.NotFound), }, { name: "Wrong id prefix", req: &pbs.GetTargetRequest{Id: "j_1234567890"}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "space in id", req: &pbs.GetTargetRequest{Id: globals.TcpTargetPrefix + "_1 23456789"}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) s, err := testService(t, ctx, conn, kms, wrapper) require.NoError(err, "Couldn't create a new host set service.") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := s.GetTarget(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "GetTarget(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) return } require.NoError(gErr) if tc.res != nil { tc.res.Item.Version = 1 } assert.Empty(cmp.Diff( got, tc.res, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "GetTarget(%q) got response %q, wanted %q", tc.req, got, tc.res) }) } } func TestList(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") sqlDB, err := conn.SqlDB(ctx) require.NoError(t, err) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } _, projNoTar := iam.TestScopes(t, iamRepo) org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") ar := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, ar.GetPublicId(), globals.AnonymousUserId) _ = iam.TestRoleGrant(t, conn, ar.GetPublicId(), "ids=*;type=target;actions=*") otherOrg, otherProj := iam.TestScopes(t, iamRepo) r = iam.TestRole(t, conn, otherProj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] otherHc := static.TestCatalogs(t, conn, otherProj.GetPublicId(), 1)[0] hss := static.TestSets(t, conn, hc.GetPublicId(), 2) otherHss := static.TestSets(t, conn, otherHc.GetPublicId(), 2) var wantTars []*pb.Target var totalTars []*pb.Target for i := 0; i < 5; i++ { name := fmt.Sprintf("tar%d", i) tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), name, target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) wantTars = append(wantTars, &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj.GetPublicId(), Name: wrapperspb.String(name), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }) totalTars = append(totalTars, wantTars[i]) tar = tcp.TestTarget(ctx, t, conn, otherProj.GetPublicId(), name, target.WithHostSources([]string{otherHss[0].GetPublicId(), otherHss[1].GetPublicId()})) totalTars = append(totalTars, &pb.Target{ Id: tar.GetPublicId(), ScopeId: otherProj.GetPublicId(), Name: wrapperspb.String(name), Scope: &scopes.ScopeInfo{Id: otherProj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: otherOrg.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }) } // Run analyze to update postgres estimates _, err = sqlDB.ExecContext(ctx, "analyze") require.NoError(t, err) // Reverse slices since response is ordered by created_time descending (newest first) slices.Reverse(wantTars) slices.Reverse(totalTars) cases := []struct { name string req *pbs.ListTargetsRequest res *pbs.ListTargetsResponse err error }{ { name: "List Many Targets", req: &pbs.ListTargetsRequest{ScopeId: proj.GetPublicId()}, res: &pbs.ListTargetsResponse{ Items: wantTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 5, }, }, { name: "List No Targets", req: &pbs.ListTargetsRequest{ScopeId: projNoTar.GetPublicId()}, res: &pbs.ListTargetsResponse{}, }, { name: "List Targets Recursively", req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true}, res: &pbs.ListTargetsResponse{ Items: totalTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 10, }, }, { name: "Paginate listing", req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true, PageSize: 2}, res: &pbs.ListTargetsResponse{ Items: totalTars[:2], ResponseType: "delta", SortBy: "created_time", SortDir: "desc", EstItemCount: 10, }, }, { name: "Filter To Many Targets", req: &pbs.ListTargetsRequest{ScopeId: scope.Global.String(), Recursive: true, Filter: fmt.Sprintf(`"/item/scope/id"==%q`, proj.GetPublicId())}, res: &pbs.ListTargetsResponse{ Items: wantTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 5, }, }, { name: "Filter To No Targets", req: &pbs.ListTargetsRequest{ScopeId: proj.GetPublicId(), Filter: `"/item/id"=="doesnt match"`}, res: &pbs.ListTargetsResponse{ ResponseType: "complete", SortBy: "created_time", SortDir: "desc", }, }, { name: "Filter Bad Format", req: &pbs.ListTargetsRequest{ScopeId: proj.GetPublicId(), Filter: `"/badformat/"`}, err: handlers.InvalidArgumentErrorf("bad format", nil), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(err, "Couldn't create new host set service.") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := s.ListTargets(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "ListTargets(%q) got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err) return } require.NoError(gErr) assert.Equal(len(tc.res.Items), len(got.Items)) wantById := make(map[string]*pb.Target, len(tc.res.Items)) for _, t := range tc.res.Items { wantById[t.Id] = t } for _, t := range got.Items { want, ok := wantById[t.Id] assert.True(ok, "Got unexpected target with id: %s", t.Id) assert.Empty(cmp.Diff( t, want, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "got %v, wanted %v", t, want) } // Test with anon user requestInfo = authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeUnknown), } requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr = s.ListTargets(ctx, tc.req) require.Error(gErr) // For now, due to how recursive checks the additional scopes, // it gets a 403 while a non-recursive expects a 401 if tc.req.GetRecursive() { assert.ErrorIs(gErr, handlers.ForbiddenError()) } else { assert.ErrorIs(gErr, handlers.UnauthenticatedError()) } }) } } func TestListGrantScopes(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") sqlDB, err := conn.SqlDB(ctx) require.NoError(t, err) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } at := authtoken.TestAuthToken(t, conn, kms, scope.Global.String()) var projects []*iam.Scope org1, proj1 := iam.TestScopes(t, iamRepo) projects = append(projects, proj1) org2, proj2 := iam.TestScopes(t, iamRepo) projects = append(projects, proj2) var totalTars []*pb.Target for i, proj := range projects { for j := 0; j < 5; j++ { name := fmt.Sprintf("tar-%d-%d", i, j) tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), name, target.WithAddress(fmt.Sprintf("1.1.%d.%d", i, j))) totalTars = append(totalTars, &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj.GetPublicId(), Name: wrapperspb.String(name), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: proj.ParentId}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{Value: fmt.Sprintf("1.1.%d.%d", i, j)}, }) } } // Run analyze to update postgres estimates _, err = sqlDB.ExecContext(ctx, "analyze") require.NoError(t, err) _ = org1 _ = org2 cases := []struct { name string pageSize uint32 setupFunc func(t *testing.T) res *pbs.ListTargetsResponse err error }{ { name: "global-with-direct-grants-wildcard", setupFunc: func(t *testing.T) { globalRole := iam.TestRole(t, conn, scope.Global.String(), iam.WithGrantScopeIds([]string{proj1.GetPublicId(), proj2.GetPublicId()})) _ = iam.TestUserRole(t, conn, globalRole.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, globalRole.GetPublicId(), "ids=*;type=*;actions=*") }, res: &pbs.ListTargetsResponse{ Items: totalTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 10, }, }, { name: "global-with-direct-grants-non-wildcard", setupFunc: func(t *testing.T) { globalRole := iam.TestRole(t, conn, scope.Global.String(), iam.WithGrantScopeIds([]string{proj1.GetPublicId(), proj2.GetPublicId()})) _ = iam.TestUserRole(t, conn, globalRole.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, globalRole.GetPublicId(), "ids=*;type=target;actions=list") _ = iam.TestRoleGrant(t, conn, globalRole.GetPublicId(), fmt.Sprintf("ids=%s,%s;actions=*", totalTars[0].Id, totalTars[1].Id)) }, res: &pbs.ListTargetsResponse{ Items: totalTars[0:2], ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 2, }, }, { name: "global-with-descendants-wildcard", setupFunc: func(t *testing.T) { globalRole := iam.TestRole(t, conn, scope.Global.String(), iam.WithGrantScopeIds([]string{globals.GrantScopeDescendants})) _ = iam.TestUserRole(t, conn, globalRole.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, globalRole.GetPublicId(), "ids=*;type=*;actions=*") }, res: &pbs.ListTargetsResponse{ Items: totalTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 10, }, }, { name: "org-with-direct-grants-wildcard", setupFunc: func(t *testing.T) { org1Role := iam.TestRole(t, conn, org1.GetPublicId(), iam.WithGrantScopeIds([]string{proj1.GetPublicId()})) _ = iam.TestUserRole(t, conn, org1Role.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, org1Role.GetPublicId(), "ids=*;type=*;actions=*") org2Role := iam.TestRole(t, conn, org2.GetPublicId(), iam.WithGrantScopeIds([]string{proj2.GetPublicId()})) _ = iam.TestUserRole(t, conn, org2Role.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, org2Role.GetPublicId(), "ids=*;type=*;actions=*") }, res: &pbs.ListTargetsResponse{ Items: totalTars, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 10, }, }, { name: "org-with-direct-grants-non-wildcard", setupFunc: func(t *testing.T) { org1Role := iam.TestRole(t, conn, org1.GetPublicId(), iam.WithGrantScopeIds([]string{proj1.GetPublicId()})) _ = iam.TestUserRole(t, conn, org1Role.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, org1Role.GetPublicId(), "ids=*;type=target;actions=list") _ = iam.TestRoleGrant(t, conn, org1Role.GetPublicId(), fmt.Sprintf("ids=%s,%s;actions=*", totalTars[0].Id, totalTars[1].Id)) org2Role := iam.TestRole(t, conn, org2.GetPublicId(), iam.WithGrantScopeIds([]string{proj2.GetPublicId()})) _ = iam.TestUserRole(t, conn, org2Role.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, org2Role.GetPublicId(), "ids=*;type=target;actions=list") _ = iam.TestRoleGrant(t, conn, org2Role.GetPublicId(), fmt.Sprintf("ids=%s,%s;actions=*", totalTars[5].Id, totalTars[6].Id)) }, res: &pbs.ListTargetsResponse{ Items: append([]*pb.Target{}, append(append([]*pb.Target{}, totalTars[0:2]...), totalTars[5:7]...)...), ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 4, }, }, { name: "org-with-children-wildcard", setupFunc: func(t *testing.T) { org1Role := iam.TestRole(t, conn, org1.GetPublicId(), iam.WithGrantScopeIds([]string{globals.GrantScopeChildren})) _ = iam.TestUserRole(t, conn, org1Role.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, org1Role.GetPublicId(), "ids=*;type=*;actions=*") }, res: &pbs.ListTargetsResponse{ Items: totalTars[0:5], ResponseType: "complete", SortBy: "created_time", SortDir: "desc", EstItemCount: 5, }, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) _, err := sqlDB.Exec("delete from iam_role") require.NoError(err) tc.setupFunc(t) s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(err, "Couldn't create new target service.") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := s.ListTargets(ctx, &pbs.ListTargetsRequest{ ScopeId: scope.Global.String(), Recursive: true, PageSize: tc.pageSize, }) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "got error %v, wanted %v", gErr, tc.err) return } require.NoError(gErr) assert.Equal(len(tc.res.Items), len(got.Items)) wantById := make(map[string]*pb.Target, len(tc.res.Items)) for _, t := range tc.res.Items { wantById[t.Id] = t } for _, t := range got.Items { want, ok := wantById[t.Id] assert.True(ok, "Got unexpected target with id: %s", t.Id) assert.Empty(cmp.Diff( t, want, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "got %v, wanted %v", t, want) } }) } } func TestListPagination(t *testing.T) { testListPagination := func(t *testing.T, useDescendants bool) { // Set database read timeout to avoid duplicates in response oldReadTimeout := globals.RefreshReadLookbackDuration globals.RefreshReadLookbackDuration = 0 t.Cleanup(func() { globals.RefreshReadLookbackDuration = oldReadTimeout }) ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") sqlDB, err := conn.SqlDB(ctx) require.NoError(t, err) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } repo, err := target.NewRepository(ctx, rw, rw, kms) require.NoError(t, err) // We're going to run the same test in two projects; one with // descendants and one with direct grants in one project and a child // grant from org in another project org1, proj1 := iam.TestScopes(t, iamRepo) org2, proj2 := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, scope.Global.String()) if useDescendants { r := iam.TestRole(t, conn, scope.Global.String(), iam.WithGrantScopeIds([]string{globals.GrantScopeDescendants})) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") } else { r1 := iam.TestRole(t, conn, proj1.GetPublicId()) _ = iam.TestUserRole(t, conn, r1.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r1.GetPublicId(), "ids=*;type=*;actions=*") r2 := iam.TestRole(t, conn, org2.GetPublicId(), iam.WithGrantScopeIds([]string{globals.GrantScopeChildren})) _ = iam.TestUserRole(t, conn, r2.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r2.GetPublicId(), "ids=*;type=*;actions=*") } hc := static.TestCatalogs(t, conn, proj1.GetPublicId(), 1)[0] hss := static.TestSets(t, conn, hc.GetPublicId(), 2) s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err) var allTargets []*pb.Target for i := 0; i < 10; i++ { tar := tcp.TestTarget(ctx, t, conn, proj1.GetPublicId(), fmt.Sprintf("tar-1-%d", i), target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) allTargets = append(allTargets, &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj1.GetPublicId(), Name: wrapperspb.String(tar.GetName()), Scope: &scopes.ScopeInfo{Id: proj1.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org1.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }) } for i := 0; i < 10; i++ { tar := tcp.TestTarget(ctx, t, conn, proj2.GetPublicId(), fmt.Sprintf("tar-2-%d", i), target.WithAddress(fmt.Sprintf("127.0.0.%d", i))) allTargets = append(allTargets, &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj2.GetPublicId(), Name: wrapperspb.String(tar.GetName()), Scope: &scopes.ScopeInfo{Id: proj2.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org2.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{Value: fmt.Sprintf("127.0.0.%d", i)}, }) } // Reverse since we read items in descending order (newest first) slices.Reverse(allTargets) // Run analyze to update postgres estimates _, err = sqlDB.ExecContext(ctx, "analyze") require.NoError(t, err) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) // Start paginating, recursively req := &pbs.ListTargetsRequest{ ScopeId: "global", Recursive: true, Filter: "", ListToken: "", PageSize: 2, } got, err := s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 2) // Compare without comparing the list token assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: allTargets[0:2], ResponseType: "delta", SortBy: "created_time", SortDir: "desc", RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Request second page req.ListToken = got.ListToken got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 2) // Compare without comparing the list token assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: allTargets[2:4], ResponseType: "delta", SortBy: "created_time", SortDir: "desc", RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Request rest of results req.ListToken = got.ListToken req.PageSize = 20 got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 16) // Compare without comparing the list token assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: allTargets[4:], ResponseType: "complete", SortBy: "created_time", SortDir: "desc", RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Create another target tar := tcp.TestTarget(ctx, t, conn, proj1.GetPublicId(), "test-target-1", target.WithHostSources([]string{hss[0].GetPublicId(), hss[1].GetPublicId()})) newTarget := &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj1.GetPublicId(), Name: wrapperspb.String(tar.GetName()), Scope: &scopes.ScopeInfo{Id: proj1.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org1.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, } // Add to the front since it's most recently updated allTargets = append([]*pb.Target{newTarget}, allTargets...) tar = tcp.TestTarget(ctx, t, conn, proj2.GetPublicId(), "test-target-2", target.WithAddress(fmt.Sprintf("127.0.0.11"))) newTarget = &pb.Target{ Id: tar.GetPublicId(), ScopeId: proj2.GetPublicId(), Name: wrapperspb.String(tar.GetName()), Scope: &scopes.ScopeInfo{Id: proj2.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org2.GetPublicId()}, CreatedTime: tar.GetCreateTime().GetTimestamp(), UpdatedTime: tar.GetUpdateTime().GetTimestamp(), Version: tar.GetVersion(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{}, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{Value: fmt.Sprintf("127.0.0.11")}, } allTargets = append([]*pb.Target{newTarget}, allTargets...) // Leaving this function here as it is very useful if test objects change /* printNames := func(step string, tars []*pb.Target) { names := make([]string, len(tars)) for i, t := range tars { names[i] = t.GetName().GetValue() } log.Println(step, pretty.Sprint(strings.Join(names, ", "))) } */ // printNames("before delete ", allTargets) // Delete one of the other targets in each project _, err = repo.DeleteTarget(ctx, allTargets[len(allTargets)-11].Id) require.NoError(t, err) deletedTarget1 := allTargets[len(allTargets)-11] allTargets = append(allTargets[:len(allTargets)-11], allTargets[len(allTargets)-11+1:]...) // printNames("after first delete ", allTargets) _, err = repo.DeleteTarget(ctx, allTargets[len(allTargets)-1].Id) require.NoError(t, err) deletedTarget2 := allTargets[len(allTargets)-1] allTargets = allTargets[:len(allTargets)-1] // printNames("after second delete", allTargets) // Update two of the other targets allTargets[2].Name = wrapperspb.String("new-name-1") allTargets[2].Version = 2 updatedTarget := &tcp.Target{ Target: &store.Target{ PublicId: allTargets[2].Id, Name: allTargets[2].Name.GetValue(), ProjectId: allTargets[2].ScopeId, }, } tg, _, err := repo.UpdateTarget(ctx, updatedTarget, 1, []string{"name"}) require.NoError(t, err) allTargets[2].UpdatedTime = tg.GetUpdateTime().GetTimestamp() allTargets[2].Version = tg.GetVersion() // Add to the front since it's most recently updated newAllTargets := append([]*pb.Target{allTargets[2]}, allTargets[0:2]...) newAllTargets = append(newAllTargets, allTargets[3:]...) allTargets = newAllTargets // printNames("after first update ", allTargets) allTargets[11].Name = wrapperspb.String("new-name-11") allTargets[11].Version = 2 updatedTarget = &tcp.Target{ Target: &store.Target{ PublicId: allTargets[11].Id, Name: allTargets[11].Name.GetValue(), ProjectId: allTargets[11].ScopeId, }, } tg, _, err = repo.UpdateTarget(ctx, updatedTarget, 1, []string{"name"}) require.NoError(t, err) allTargets[11].UpdatedTime = tg.GetUpdateTime().GetTimestamp() allTargets[11].Version = tg.GetVersion() // Add to the front since it's most recently updated newAllTargets = append([]*pb.Target{allTargets[11]}, allTargets[0:11]...) newAllTargets = append(newAllTargets, allTargets[12:]...) allTargets = newAllTargets // printNames("after second update", allTargets) // Run analyze to update postgres estimates _, err = sqlDB.ExecContext(ctx, "analyze") require.NoError(t, err) // Request updated results req.ListToken = got.ListToken req.PageSize = 2 got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 2) // Compare without comparing the list token assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: []*pb.Target{allTargets[0], allTargets[1]}, ResponseType: "delta", SortBy: "updated_time", SortDir: "desc", // Should contain the deleted target RemovedIds: []string{deletedTarget1.Id, deletedTarget2.Id}, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Get next page req.ListToken = got.ListToken got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 2) // Compare without comparing the list token assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: []*pb.Target{allTargets[2], allTargets[3]}, ResponseType: "complete", SortBy: "updated_time", SortDir: "desc", RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Request new page with filter requiring looping // to fill the page. req.ListToken = "" req.PageSize = 1 req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, allTargets[len(allTargets)-2].Id, allTargets[len(allTargets)-1].Id) got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 1) assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: []*pb.Target{allTargets[len(allTargets)-2]}, ResponseType: "delta", SortBy: "created_time", SortDir: "desc", // Should be empty again RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) req.ListToken = got.ListToken // Get the second page got, err = s.ListTargets(ctx, req) require.NoError(t, err) require.Len(t, got.GetItems(), 1) assert.Empty(t, cmp.Diff( got, &pbs.ListTargetsResponse{ Items: []*pb.Target{allTargets[len(allTargets)-1]}, ResponseType: "complete", SortBy: "created_time", SortDir: "desc", RemovedIds: nil, EstItemCount: 20, }, cmpopts.SortSlices(func(a, b string) bool { return a < b }), protocmp.Transform(), protocmp.IgnoreFields(&pbs.ListTargetsResponse{}, "list_token"), ), ) // Create unauthenticated user unauthAt := authtoken.TestAuthToken(t, conn, kms, org1.GetPublicId()) unauthR := iam.TestRole(t, conn, proj1.GetPublicId()) _ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId()) // Make a request with the unauthenticated user, // ensure the response contains the pagination parameters. requestInfo = authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: unauthAt.GetPublicId(), Token: unauthAt.GetToken(), } requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err = s.ListTargets(ctx, &pbs.ListTargetsRequest{ ScopeId: "global", Recursive: true, }) require.NoError(t, err) assert.Empty(t, got.Items) assert.Equal(t, "created_time", got.SortBy) assert.Equal(t, "desc", got.SortDir) assert.Equal(t, "complete", got.ResponseType) } t.Run("with-descendants", func(t *testing.T) { testListPagination(t, true) }) t.Run("without-descendants", func(t *testing.T) { testListPagination(t, false) }) } func TestDelete(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test") s, err := testService(t, ctx, conn, kms, wrapper) require.NoError(t, err, "Couldn't create a new target service.") cases := []struct { name string scopeId string req *pbs.DeleteTargetRequest res *pbs.DeleteTargetResponse err error }{ { name: "Delete an Existing Target", scopeId: proj.GetPublicId(), req: &pbs.DeleteTargetRequest{ Id: tar.GetPublicId(), }, }, { name: "Delete Not Existing Target", scopeId: proj.GetPublicId(), req: &pbs.DeleteTargetRequest{ Id: globals.TcpTargetPrefix + "_doesntexis", }, err: handlers.ApiErrorWithCode(codes.NotFound), }, { name: "Bad target id formatting", scopeId: proj.GetPublicId(), req: &pbs.DeleteTargetRequest{ Id: globals.TcpTargetPrefix + "_bad_format", }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := s.DeleteTarget(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "DeleteTarget(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } assert.Empty(cmp.Diff( tc.res, got, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "DeleteTarget(%q) got response %q, wanted %q", tc.req, got, tc.res) }) } } func TestDelete_twice(t *testing.T) { t.Parallel() assert, require := assert.New(t), require.New(t) conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) ctx := context.Background() iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test") s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(err, "Couldn't create a new target service.") req := &pbs.DeleteTargetRequest{ Id: tar.GetPublicId(), } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.DeleteTarget(ctx, req) assert.NoError(gErr, "First attempt") _, gErr = s.DeleteTarget(ctx, req) assert.Error(gErr, "Second attempt") assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected permission denied for the second delete.") } func TestCreate(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, "global") _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") _ = iam.TestRoleGrantScope(t, conn, r.GetPublicId(), globals.GrantScopeDescendants) // Ensure we are using the OSS worker filter function. This prevents us from // running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) cases := []struct { name string req *pbs.CreateTargetRequest res *pbs.CreateTargetResponse err error errStr string }{ { name: "Create a valid target", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, EgressWorkerFilter: wrapperspb.String(`type == "bar"`), }}, res: &pbs.CreateTargetResponse{ Uri: fmt.Sprintf("targets/%s_", globals.TcpTargetPrefix), Item: &pb.Target{ ScopeId: proj.GetPublicId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, EgressWorkerFilter: wrapperspb.String(`type == "bar"`), Address: &wrapperspb.StringValue{}, }, }, }, { name: "Create a valid target with two aliases", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("target_with_aliases"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Value: "create-two-aliasses1", ScopeId: "global", }, { Value: "create-two-aliasses2", ScopeId: "global", }, }, }}, res: &pbs.CreateTargetResponse{ Uri: fmt.Sprintf("targets/%s_", globals.TcpTargetPrefix), Item: &pb.Target{ ScopeId: proj.GetPublicId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("target_with_aliases"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, Aliases: []*pb.Alias{ { Value: "create-two-aliasses1", }, { Value: "create-two-aliasses2", }, }, SessionMaxSeconds: wrapperspb.UInt32(28800), SessionConnectionLimit: wrapperspb.Int32(-1), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "Create a target with alias specifying the id", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("target_with_invalid_alias"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Id: "alt_1234567890", Value: "id-specified", ScopeId: "global", }, }, }}, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Create a target with invalid alias", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("target_with_invalid_alias"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Value: "invalid alias", ScopeId: "global", }, }, }}, errStr: "unable to create target alias", }, { name: "Create a target with invalid scope", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("target_with_invalid_alias"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Value: "alias.invalid.scope", ScopeId: proj.GetPublicId(), }, }, }}, errStr: "unable to create target alias", }, { name: "Create a target with duplicate aliasses", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("target_with_invalid_alias"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Value: "duplicate-alias", ScopeId: "global", }, { Value: "duplicate-alias", ScopeId: "global", }, }, }}, errStr: "duplicate key value violates unique constraint", }, { name: "Create a target with no port", req: &pbs.CreateTargetRequest{Item: &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), EgressWorkerFilter: wrapperspb.String(`type == "bar"`), }}, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Create with default port 0", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, }}, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Create with unknown type", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: "ThisIsMadeUp", }}, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Create with no type", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), }}, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Can't specify Id", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Id: "not allowed to be set", }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Can't specify Created Time", req: &pbs.CreateTargetRequest{Item: &pb.Target{ CreatedTime: timestamppb.Now(), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Can't specify Update Time", req: &pbs.CreateTargetRequest{Item: &pb.Target{ UpdatedTime: timestamppb.Now(), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid worker filter expression", req: &pbs.CreateTargetRequest{Item: &pb.Target{ EgressWorkerFilter: wrapperspb.String("bad expression"), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Deprecated worker filter", req: &pbs.CreateTargetRequest{Item: &pb.Target{ WorkerFilter: wrapperspb.String(`"/name" matches "test-worker"`), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), errStr: expectedDeprecatedWorkerFilterError, }, { name: "Ingress filter unsupported on OSS", req: &pbs.CreateTargetRequest{Item: &pb.Target{ IngressWorkerFilter: wrapperspb.String(`"/name" matches "test-worker"`), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address length", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Address: wrapperspb.String("ab"), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address w/ port", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Address: wrapperspb.String("8.8.8.8:80"), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address not parsable", req: &pbs.CreateTargetRequest{Item: &pb.Target{ Address: wrapperspb.String("aaa.8bc.8.8:80:abc"), }}, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(err, "Failed to create a new host set service.") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := s.CreateTarget(ctx, tc.req) if tc.err != nil || tc.errStr != "" { require.Error(gErr) if tc.err != nil { assert.True(errors.Is(gErr, tc.err), "CreateTarget(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } if tc.errStr != "" { assert.ErrorContains(gErr, tc.errStr) } } else { assert.Nil(gErr, "Unexpected err: %v", gErr) } if got != nil { assert.Contains(got.GetUri(), tc.res.GetUri()) assert.True(strings.HasPrefix(got.GetItem().GetId(), globals.TcpTargetPrefix), got.GetItem().GetId()) // Clear all values which are hard to compare against. got.Uri, tc.res.Uri = "", "" got.Item.Id, tc.res.Item.Id = "", "" got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil } if tc.res != nil { tc.res.Item.Version = 1 } assert.Empty(cmp.Diff( got, tc.res, protocmp.Transform(), // These are generated, so we don't know what they are when // specifying the expected case. protocmp.IgnoreFields(&pb.Alias{}, "id"), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "CreateTarget(%q)\n got response %q\n, wanted %q\n", tc.req, got, tc.res) }) } } func TestCreate_AliasAuthCheck(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) // Both users can create targets allProjectR := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, allProjectR.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, allProjectR.GetPublicId(), "ids=*;type=*;actions=*") req := &pb.Target{ ScopeId: proj.GetPublicId(), Name: wrapperspb.String("name"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), }, }, WithAliases: []*pb.Alias{ { Value: "alias-allowed", ScopeId: "global", }, }, } // Ensure we are using the OSS worker filter function. This prevents us from // running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Failed to create a new host set service.") t.Run("disallowed user creating target", func(t *testing.T) { requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) resp, err := s.CreateTarget(ctx, &pbs.CreateTargetRequest{Item: req}) require.Error(t, err) require.Nil(t, resp) assert.ErrorContains(t, err, "PermissionDenied") }) t.Run("allowed user creating a target", func(t *testing.T) { // only aliasAllowedAt can create aliases aliasR := iam.TestRole(t, conn, "global") _ = iam.TestUserRole(t, conn, aliasR.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, aliasR.GetPublicId(), "ids=*;type=alias;actions=*") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) resp, err := s.CreateTarget(ctx, &pbs.CreateTargetRequest{Item: req}) require.NoError(t, err) require.NotNil(t, resp) }) } func TestUpdate(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms) } repo, err := repoFn() require.NoError(t, err, "Couldn't create new target repo.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) hostSourceIds := []string{hs[0].GetPublicId(), hs[1].GetPublicId()} hostSources := []*pb.HostSource{ {Id: hs[0].GetPublicId(), HostCatalogId: hs[0].GetCatalogId()}, {Id: hs[1].GetPublicId(), HostCatalogId: hs[1].GetCatalogId()}, } ttar, err := target.New(ctx, tcp.Subtype, proj.GetPublicId(), target.WithName("default"), target.WithDescription("default"), target.WithSessionMaxSeconds(1), target.WithSessionConnectionLimit(1), target.WithDefaultPort(2), target.WithDefaultClientPort(3)) require.NoError(t, err) tar, err := repo.CreateTarget(context.Background(), ttar) require.NoError(t, err) tar, err = repo.AddTargetHostSources(context.Background(), tar.GetPublicId(), tar.GetVersion(), []string{hs[0].GetPublicId(), hs[1].GetPublicId()}) require.NoError(t, err) resetTarget := func() { itar, err := repo.LookupTarget(context.Background(), tar.GetPublicId()) require.NoError(t, err) tar, _, err = repo.UpdateTarget(context.Background(), tar, itar.GetVersion(), []string{"Name", "Description", "SessionMaxSeconds", "SessionConnectionLimit", "DefaultPort", "DefaultClientPort"}) require.NoError(t, err, "Failed to reset target.") } hCreated := tar.GetCreateTime().GetTimestamp().AsTime() toMerge := &pbs.UpdateTargetRequest{ Id: tar.GetPublicId(), } tested, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Failed to create a new host set service.") // Ensure we are using the OSS worker filter functions. This prevents us // from running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) validateIngressFn := targets.ValidateIngressWorkerFilterFn targets.ValidateIngressWorkerFilterFn = targets.IngressWorkerFilterUnsupported cases := []struct { name string req *pbs.UpdateTargetRequest res *pbs.UpdateTargetResponse err error }{ { name: "Update an Existing Target", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"name", "description", "session_max_seconds", "session_connection_limit", "type"}, }, Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), SessionMaxSeconds: wrapperspb.UInt32(3600), SessionConnectionLimit: wrapperspb.Int32(5), Type: tcp.Subtype.String(), }, }, res: &pbs.UpdateTargetResponse{ Item: &pb.Target{ Id: tar.GetPublicId(), ScopeId: tar.GetProjectId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, CreatedTime: tar.GetCreateTime().GetTimestamp(), HostSourceIds: hostSourceIds, HostSources: hostSources, SessionMaxSeconds: wrapperspb.UInt32(3600), SessionConnectionLimit: wrapperspb.Int32(5), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "Multiple Paths in single string", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"name,description,type"}, }, Item: &pb.Target{ Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), Type: tcp.Subtype.String(), }, }, res: &pbs.UpdateTargetResponse{ Item: &pb.Target{ Id: tar.GetPublicId(), ScopeId: tar.GetProjectId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("name"), Description: wrapperspb.String("desc"), CreatedTime: tar.GetCreateTime().GetTimestamp(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, HostSourceIds: hostSourceIds, HostSources: hostSources, SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()), SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "No Update Mask", req: &pbs.UpdateTargetRequest{ Item: &pb.Target{ Name: wrapperspb.String("updated name"), Description: wrapperspb.String("updated desc"), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Empty Path", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{Paths: []string{}}, Item: &pb.Target{ Name: wrapperspb.String("updated name"), Description: wrapperspb.String("updated desc"), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Update port to 0", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{Paths: []string{"attributes.default_port"}}, Item: &pb.Target{ Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(0), DefaultClientPort: wrapperspb.UInt32(3), }, }, }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Clear port", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{Paths: []string{"attributes.default_port"}}, Item: &pb.Target{}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Only non-existent paths in Mask", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistent_field"}}, Item: &pb.Target{ Name: wrapperspb.String("updated name"), Description: wrapperspb.String("updated desc"), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Unset Name", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"name"}, }, Item: &pb.Target{ Description: wrapperspb.String("ignored"), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Unset Description", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"description"}, }, Item: &pb.Target{ Name: wrapperspb.String("ignored"), }, }, res: &pbs.UpdateTargetResponse{ Item: &pb.Target{ Id: tar.GetPublicId(), ScopeId: tar.GetProjectId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("default"), CreatedTime: tar.GetCreateTime().GetTimestamp(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, HostSourceIds: hostSourceIds, HostSources: hostSources, SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()), SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "Update Only Name", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"name"}, }, Item: &pb.Target{ Name: wrapperspb.String("updated"), Description: wrapperspb.String("ignored"), }, }, res: &pbs.UpdateTargetResponse{ Item: &pb.Target{ Id: tar.GetPublicId(), ScopeId: tar.GetProjectId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("updated"), Description: wrapperspb.String("default"), CreatedTime: tar.GetCreateTime().GetTimestamp(), Type: tcp.Subtype.String(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, HostSourceIds: hostSourceIds, HostSources: hostSources, SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()), SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "Update Only Description", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"description"}, }, Item: &pb.Target{ Name: wrapperspb.String("ignored"), Description: wrapperspb.String("notignored"), }, }, res: &pbs.UpdateTargetResponse{ Item: &pb.Target{ Id: tar.GetPublicId(), ScopeId: tar.GetProjectId(), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("default"), Description: wrapperspb.String("notignored"), CreatedTime: tar.GetCreateTime().GetTimestamp(), Attrs: &pb.Target_TcpTargetAttributes{ TcpTargetAttributes: &pb.TcpTargetAttributes{ DefaultPort: wrapperspb.UInt32(2), DefaultClientPort: wrapperspb.UInt32(3), }, }, Type: tcp.Subtype.String(), HostSourceIds: hostSourceIds, HostSources: hostSources, SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()), SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()), AuthorizedActions: testAuthorizedActions, Address: &wrapperspb.StringValue{}, }, }, }, { name: "Update WithAlias", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"with_alias"}, }, Item: &pb.Target{ Name: wrapperspb.String("ignored"), WithAliases: []*pb.Alias{{Value: "new-alias", ScopeId: "global"}}, }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Update a Non Existing Target", req: &pbs.UpdateTargetRequest{ Id: globals.TcpTargetPrefix + "_DoesntExis", UpdateMask: &field_mask.FieldMask{ Paths: []string{"description"}, }, Item: &pb.Target{ Name: wrapperspb.String("new"), Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Description: wrapperspb.String("desc"), }, }, err: handlers.ApiErrorWithCode(codes.NotFound), }, { name: "Cant change Id", req: &pbs.UpdateTargetRequest{ Id: hc.GetPublicId(), UpdateMask: &field_mask.FieldMask{ Paths: []string{"id"}, }, Item: &pb.Target{ Id: "p_somethinge", Scope: &scopes.ScopeInfo{Id: proj.GetPublicId(), Type: scope.Project.String(), ParentScopeId: org.GetPublicId()}, Name: wrapperspb.String("new"), Description: wrapperspb.String("new desc"), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Cant specify Created Time", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"created_time"}, }, Item: &pb.Target{ CreatedTime: timestamppb.Now(), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Cant specify Updated Time", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"updated_time"}, }, Item: &pb.Target{ UpdatedTime: timestamppb.Now(), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Ingress filter unsupported", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"ingress_worker_filter"}, }, Item: &pb.Target{ IngressWorkerFilter: wrapperspb.String(`type == "bar"`), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Can't update worker filter and egress filter", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"worker_filter", "egress_worker_filter"}, }, Item: &pb.Target{ WorkerFilter: wrapperspb.String(`type == "bar"`), EgressWorkerFilter: wrapperspb.String(`type == "bar"`), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address length", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"address"}, }, Item: &pb.Target{ Address: wrapperspb.String("ab"), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address w/ port", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"address"}, }, Item: &pb.Target{ Address: wrapperspb.String("8.8.8.8:80"), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid address not parsable", req: &pbs.UpdateTargetRequest{ UpdateMask: &field_mask.FieldMask{ Paths: []string{"address"}, }, Item: &pb.Target{ Address: wrapperspb.String("aaa.8bc.8.8:80:abc"), }, }, res: nil, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { defer resetTarget() assert, require := assert.New(t), require.New(t) tc.req.Item.Version = tar.GetVersion() req := proto.Clone(toMerge).(*pbs.UpdateTargetRequest) proto.Merge(req, tc.req) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, gErr := tested.UpdateTarget(ctx, req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "UpdateTarget(%+v) got error %v, wanted %v", req, gErr, tc.err) return } require.NoError(gErr) if got != nil { assert.NotNilf(tc.res, "Expected UpdateHost response to be nil, but was %v", got) gotUpdateTime := got.GetItem().GetUpdatedTime().AsTime() // Verify it is a set updated after it was created // TODO: This is currently failing. assert.True(gotUpdateTime.After(hCreated), "Updated target should have been updated after it's creation. Was updated %v, which is after %v", gotUpdateTime, hCreated) // Clear all values which are hard to compare against. got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil } if tc.res != nil { tc.res.Item.Version = tc.req.Item.Version + 1 } assert.Empty(cmp.Diff( got, tc.res, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "UpdateTarget(%q) got response %q, wanted %q", req, got, tc.res) }) } // Reset worker filter funcs targets.ValidateIngressWorkerFilterFn = validateIngressFn } func TestUpdate_BadVersion(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms) } repo, err := repoFn() require.NoError(t, err, "Couldn't create new target repo.") ttar, err := target.New(ctx, tcp.Subtype, proj.GetPublicId(), target.WithName("default"), target.WithDescription("default")) tar := ttar.(*tcp.Target) tar.DefaultPort = 2 tar.DefaultClientPort = 3 require.NoError(t, err) gtar, err := repo.CreateTarget(context.Background(), tar) require.NoError(t, err) tested, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Failed to create a new host set service.") requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) upTar, err := tested.UpdateTarget(ctx, &pbs.UpdateTargetRequest{ Id: gtar.GetPublicId(), Item: &pb.Target{ Description: wrapperspb.String("updated"), Version: 72, }, UpdateMask: &field_mask.FieldMask{Paths: []string{"description"}}, }) assert.Nil(t, upTar) assert.Error(t, err) assert.True(t, errors.Is(err, handlers.NotFoundError()), "Got %v, wanted not found error.", err) } func TestAddTargetHostSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) sche := scheduler.TestScheduler(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) plg := plugin.TestPlugin(t, conn, "test") pluginHc := hostplugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) pluginHs := hostplugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ plg.GetPublicId(): loopback.NewWrappingPluginHostClient(&loopback.TestPluginServer{}), }) addCases := []struct { name string tar target.Target addHostSources []string resultHostSources []string }{ { name: "Add set on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty"), addHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Add set on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add duplicated sets on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, }, { name: "Add plugin set on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "plugin empty"), addHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{pluginHs.GetPublicId()}, }, { name: "Add plugin set on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), addHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId(), pluginHs.GetPublicId()}, }, } for _, tc := range addCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.AddTargetHostSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), HostSourceIds: tc.addHostSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.AddTargetHostSources(ctx, req) s, ok := status.FromError(err) require.True(t, ok) require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultHostSources, got.GetItem().GetHostSourceIds()) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test") failCases := []struct { name string req *pbs.AddTargetHostSourcesRequest err error }{ { name: "Bad Target Id", req: &pbs.AddTargetHostSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Bad version", req: &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 2, HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Empty host set list", req: &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Incorrect host set ids", req: &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{"incorrect"}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.AddTargetHostSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "AddTargetHostSources(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestSetTargetHostSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) sche := scheduler.TestScheduler(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Error when getting new host set service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) plg := plugin.TestPlugin(t, conn, "test") pluginHc := hostplugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) pluginHs := hostplugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ plg.GetPublicId(): loopback.NewWrappingPluginHostClient(&loopback.TestPluginServer{}), }) setCases := []struct { name string tar target.Target setHostSources []string resultHostSources []string }{ { name: "Set on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty"), setHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set plugin set on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "plugin populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{pluginHs.GetPublicId()}, }, { name: "Set duplicate host set on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[1].GetPublicId()}, }, { name: "Set empty on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated", target.WithHostSources([]string{hs[0].GetPublicId()})), setHostSources: []string{}, resultHostSources: nil, }, } for _, tc := range setCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.SetTargetHostSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), HostSourceIds: tc.setHostSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.SetTargetHostSources(ctx, req) require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultHostSources, got.GetItem().GetHostSourceIds()) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string req *pbs.SetTargetHostSourcesRequest err error }{ { name: "Bad target Id", req: &pbs.SetTargetHostSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Bad version", req: &pbs.SetTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 3, HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Bad host set id", req: &pbs.SetTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{"invalid"}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.SetTargetHostSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "SetTargetHostSources(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestRemoveTargetHostSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) sche := scheduler.TestScheduler(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Error when getting new host set service.") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 2) plg := plugin.TestPlugin(t, conn, "test") pluginHc := hostplugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) pluginHs := hostplugin.TestSet(t, conn, kms, sche, pluginHc, map[string]plgpb.HostPluginServiceClient{ plg.GetPublicId(): loopback.NewWrappingPluginHostClient(&loopback.TestPluginServer{}), }) removeCases := []struct { name string tar target.Target removeHostSources []string resultHostSources []string wantErr bool }{ { name: "Remove from empty", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty"), removeHostSources: []string{hs[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 sets", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove 1 plugin set of 2 sets", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove plugin partial", target.WithHostSources([]string{hs[0].GetPublicId(), pluginHs.GetPublicId()})), removeHostSources: []string{pluginHs.GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 sets", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[1].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{hs[0].GetPublicId()}, }, { name: "Remove all hosts from set", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove all", target.WithHostSources([]string{hs[0].GetPublicId(), hs[1].GetPublicId()})), removeHostSources: []string{hs[0].GetPublicId(), hs[1].GetPublicId()}, resultHostSources: []string{}, }, } for _, tc := range removeCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.RemoveTargetHostSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), HostSourceIds: tc.removeHostSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.RemoveTargetHostSources(ctx, req) if tc.wantErr { assert.Error(t, err) return } s, ok := status.FromError(err) require.True(t, ok) require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultHostSources, got.GetItem().GetHostSourceIds()) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string req *pbs.RemoveTargetHostSourcesRequest err error }{ { name: "Bad version", req: &pbs.RemoveTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 3, HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Bad target Id", req: &pbs.RemoveTargetHostSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), HostSourceIds: []string{hs[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "empty sets", req: &pbs.RemoveTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid set ids", req: &pbs.RemoveTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{"invalid"}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.RemoveTargetHostSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "RemoveTargetHostSets(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestAddTargetCredentialSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, context.Background(), conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] cls := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) addCases := []struct { name string tar target.Target addSources []string resultSourceIds []string }{ { name: "Add library on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for lib sources"), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Add static cred on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty for static sources"), addSources: []string{creds[1].GetPublicId()}, resultSourceIds: []string{creds[1].GetPublicId()}, }, { name: "Add library on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId()}, }, { name: "Add library on static cred populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for lib-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{cls[1].GetPublicId()}, resultSourceIds: []string{creds[0].GetPublicId(), cls[1].GetPublicId()}, }, { name: "Add static cred on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{creds[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), creds[1].GetPublicId()}, }, { name: "Add static cred on static cred populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated for static-static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{creds[1].GetPublicId()}, resultSourceIds: []string{creds[0].GetPublicId(), creds[1].GetPublicId()}, }, { name: "Add duplicated sources on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for lib sources", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId(), creds[1].GetPublicId()}, resultSourceIds: []string{cls[0].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId()}, }, { name: "Add duplicated sources on static cred populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicated for static sources", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), addSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId(), creds[1].GetPublicId()}, resultSourceIds: []string{creds[0].GetPublicId(), cls[1].GetPublicId(), creds[1].GetPublicId()}, }, } for _, tc := range addCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.AddTargetCredentialSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), BrokeredCredentialSourceIds: tc.addSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.AddTargetCredentialSources(ctx, req) require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultSourceIds, got.GetItem().GetBrokeredCredentialSourceIds()) assert.Equal(t, len(tc.resultSourceIds), len(got.GetItem().GetBrokeredCredentialSources())) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test") failCases := []struct { name string req *pbs.AddTargetCredentialSourcesRequest err error }{ { name: "Bad target id", req: &pbs.AddTargetCredentialSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{ cls[0].GetPublicId(), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Bad version", req: &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 2, BrokeredCredentialSourceIds: []string{ cls[0].GetPublicId(), }, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Empty source list", req: &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Incorrect source id", req: &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{"incorrect"}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.AddTargetCredentialSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "AddTargetCredentialSources(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestSetTargetCredentialSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, ctx, conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") storeVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] cls := vault.TestCredentialLibraries(t, conn, wrapper, storeVault.GetPublicId(), 2) storeStatic := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "user", "pass", storeStatic.GetPublicId(), proj.GetPublicId(), 2) setCases := []struct { name string tar target.Target setCredentialSources []string resultCredentialSourceIds []string }{ { name: "Set library on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty library"), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set static on empty target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static"), setCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, { name: "Set library on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set static on library populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, { name: "Set library on static populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated library-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set static on static populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "populated static-static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, { name: "Set duplicate library on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate library", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{cls[1].GetPublicId(), cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[1].GetPublicId()}, }, { name: "Set duplicate static on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "duplicate static", target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{creds[1].GetPublicId(), creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[1].GetPublicId()}, }, { name: "Set empty on populated target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "another populated", target.WithCredentialLibraries([]*target.CredentialLibrary{target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose)}), target.WithStaticCredentials([]*target.StaticCredential{target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose)})), setCredentialSources: []string{}, resultCredentialSourceIds: nil, }, } for _, tc := range setCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.SetTargetCredentialSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), BrokeredCredentialSourceIds: tc.setCredentialSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.SetTargetCredentialSources(ctx, req) require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultCredentialSourceIds, got.GetItem().GetBrokeredCredentialSourceIds()) assert.Equal(t, len(tc.resultCredentialSourceIds), len(got.GetItem().GetBrokeredCredentialSources())) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "test name") failCases := []struct { name string req *pbs.SetTargetCredentialSourcesRequest err error }{ { name: "Bad target Id", req: &pbs.SetTargetCredentialSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{cls[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Bad version", req: &pbs.SetTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 3, BrokeredCredentialSourceIds: []string{cls[0].GetPublicId()}, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Bad source id", req: &pbs.SetTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{"invalid"}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.SetTargetCredentialSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "SetTargetCredentialSources(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestRemoveTargetCredentialSources(t *testing.T) { t.Parallel() ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) rw := db.New(conn) iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } tokenRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") s, err := testService(t, ctx, conn, kms, wrapper) require.NoError(t, err, "Error when getting new target service.") csVault := vault.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] cls := vault.TestCredentialLibraries(t, conn, wrapper, csVault.GetPublicId(), 2) csStatic := credstatic.TestCredentialStores(t, conn, wrapper, proj.GetPublicId(), 1)[0] creds := credstatic.TestUsernamePasswordCredentials(t, conn, wrapper, "u", "p", csStatic.GetPublicId(), proj.GetPublicId(), 2) removeCases := []struct { name string tar target.Target removeCredentialSources []string resultCredentialSourceIds []string wantErr bool }{ { name: "Remove library from empty", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty lib"), removeCredentialSources: []string{cls[1].GetPublicId()}, wantErr: true, }, { name: "Remove static from empty", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "empty static"), removeCredentialSources: []string{creds[1].GetPublicId()}, wantErr: true, }, { name: "Remove 1 of 2 libraries", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial lib", target.WithCredentialLibraries([]*target.CredentialLibrary{ target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{cls[1].GetPublicId()}, resultCredentialSourceIds: []string{cls[0].GetPublicId()}, }, { name: "Remove 1 of 2 static credentials", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove partial static", target.WithStaticCredentials([]*target.StaticCredential{ target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{creds[1].GetPublicId()}, resultCredentialSourceIds: []string{creds[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 libraries", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate lib", target.WithCredentialLibraries([]*target.CredentialLibrary{ target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ cls[1].GetPublicId(), cls[1].GetPublicId(), }, resultCredentialSourceIds: []string{cls[0].GetPublicId()}, }, { name: "Remove 1 duplicate set of 2 static credentials", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove duplicate static", target.WithStaticCredentials([]*target.StaticCredential{ target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ creds[1].GetPublicId(), creds[1].GetPublicId(), }, resultCredentialSourceIds: []string{creds[0].GetPublicId()}, }, { name: "Remove mixed sources from target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove mixed", target.WithCredentialLibraries([]*target.CredentialLibrary{ target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.BrokeredPurpose), }), target.WithStaticCredentials([]*target.StaticCredential{ target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ cls[1].GetPublicId(), creds[0].GetPublicId(), }, resultCredentialSourceIds: []string{ cls[0].GetPublicId(), creds[1].GetPublicId(), }, }, { name: "Remove all sources from target", tar: tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "remove all", target.WithCredentialLibraries([]*target.CredentialLibrary{ target.TestNewCredentialLibrary("", cls[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewCredentialLibrary("", cls[1].GetPublicId(), credential.BrokeredPurpose), }), target.WithStaticCredentials([]*target.StaticCredential{ target.TestNewStaticCredential("", creds[0].GetPublicId(), credential.BrokeredPurpose), target.TestNewStaticCredential("", creds[1].GetPublicId(), credential.BrokeredPurpose), })), removeCredentialSources: []string{ cls[0].GetPublicId(), cls[1].GetPublicId(), creds[0].GetPublicId(), creds[1].GetPublicId(), }, resultCredentialSourceIds: []string{}, }, } for _, tc := range removeCases { t.Run(tc.name, func(t *testing.T) { req := &pbs.RemoveTargetCredentialSourcesRequest{ Id: tc.tar.GetPublicId(), Version: tc.tar.GetVersion(), BrokeredCredentialSourceIds: tc.removeCredentialSources, } requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) got, err := s.RemoveTargetCredentialSources(ctx, req) if tc.wantErr { assert.Error(t, err) return } require.NoError(t, err, "Got error: %v", s) assert.ElementsMatch(t, tc.resultCredentialSourceIds, got.GetItem().GetBrokeredCredentialSourceIds()) assert.Equal(t, len(tc.resultCredentialSourceIds), len(got.GetItem().GetBrokeredCredentialSources())) }) } tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), "testing") failCases := []struct { name string req *pbs.RemoveTargetCredentialSourcesRequest err error }{ { name: "Bad version", req: &pbs.RemoveTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion() + 3, BrokeredCredentialSourceIds: []string{ cls[0].GetPublicId(), }, }, err: handlers.ApiErrorWithCode(codes.Internal), }, { name: "Bad target Id", req: &pbs.RemoveTargetCredentialSourcesRequest{ Id: "bad id", Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{ cls[0].GetPublicId(), }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Empty sources", req: &pbs.RemoveTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{}, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, { name: "Invalid source ids", req: &pbs.RemoveTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), BrokeredCredentialSourceIds: []string{ "invalid", }, }, err: handlers.ApiErrorWithCode(codes.InvalidArgument), }, } for _, tc := range failCases { t.Run(tc.name, func(t *testing.T) { assert, require := assert.New(t), require.New(t) requestInfo := authpb.RequestInfo{ TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), Token: at.GetToken(), } requestContext := context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{}) ctx := auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo) _, gErr := s.RemoveTargetCredentialSources(ctx, tc.req) if tc.err != nil { require.Error(gErr) assert.True(errors.Is(gErr, tc.err), "RemoveTargetCredentialSources(%+v) got error %v, wanted %v", tc.req, gErr, tc.err) } }) } } func TestAuthorizeSession(t *testing.T) { ctx := context.Background() // This prevents us from running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) sche := scheduler.TestScheduler(t, conn, wrapper) err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) { return session.NewRepository(ctx, rw, rw, kms, opts...) } staticRepo, err := static.NewRepository(ctx, rw, rw, kms) require.NoError(t, err) staticHostRepoFn := func() (*static.Repository, error) { return staticRepo, nil } vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } staticCredRepoFn := func() (*credstatic.Repository, error) { return credstatic.NewRepository(ctx, rw, rw, kms) } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } passwordAuthRepoFn := func() (*password.Repository, error) { return password.NewRepository(ctx, rw, rw, kms) } oidcAuthRepoFn := func() (*oidc.Repository, error) { return oidc.NewRepository(ctx, rw, rw, kms) } ldapAuthRepoFn := func() (*ldap.Repository, error) { return ldap.NewRepository(ctx, rw, rw, kms) } targetAliasRepoFn := func() (*talias.Repository, error) { return talias.NewRepository(ctx, rw, rw, kms) } plg := plugin.TestPlugin(t, conn, "test") plgm := map[string]plgpb.HostPluginServiceClient{ plg.GetPublicId(): loopback.NewWrappingPluginHostClient(loopback.TestPluginHostServer{ ListHostsFn: func(_ context.Context, req *plgpb.ListHostsRequest) (*plgpb.ListHostsResponse, error) { var setIds []string for _, set := range req.GetSets() { setIds = append(setIds, set.GetId()) } return &plgpb.ListHostsResponse{Hosts: []*plgpb.ListHostsResponseHost{ { SetIds: setIds, ExternalId: "test", IpAddresses: []string{"10.0.0.1", "192.168.0.1"}, DnsNames: []string{"example.com"}, }, { SetIds: setIds, ExternalId: "test2", IpAddresses: []string{"10.1.1.1", "192.168.1.1"}, DnsNames: []string{"another-example.com"}, }, }}, nil }, }), } pluginHostRepoFn := func() (*hostplugin.Repository, error) { return hostplugin.NewRepository(ctx, rw, rw, kms, sche, plgm) } loginName := "foo@bar.com" accountName := "passname" userName := "username" org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId(), authtoken.WithPasswordOptions(password.WithLoginName(loginName), password.WithName(accountName)), authtoken.WithIamOptions(iam.WithName(userName))) ctx = auth.NewVerifierContextWithAccounts(requests.NewRequestContext(ctx), iamRepoFn, atRepoFn, serversRepoFn, passwordAuthRepoFn, oidcAuthRepoFn, ldapAuthRepoFn, kms, &authpb.RequestInfo{ Token: at.GetToken(), TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] shs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, shs.GetPublicId(), []*static.Host{h}) hcWithPort := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hWithPort := static.TestHosts(t, conn, hcWithPort.GetPublicId(), 1)[0] shsWithPort := static.TestSets(t, conn, hcWithPort.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, shsWithPort.GetPublicId(), []*static.Host{hWithPort}) hWithPortBareAddress := hWithPort.GetAddress() hWithPort, _, err = staticRepo.UpdateHost(ctx, hcWithPort.GetProjectId(), hWithPort, hWithPort.GetVersion(), []string{"address"}) require.NoError(t, err) phc := hostplugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId()) phs := hostplugin.TestSet(t, conn, kms, sche, phc, plgm, hostplugin.WithPreferredEndpoints([]string{"cidr:10.0.0.0/24"})) // Sync the boundary db from the plugins hostplugin.TestRunSetSync(t, conn, kms, plgm) v := vault.NewTestVaultServer(t) v.MountPKI(t, vault.WithTestMountPath("pki/"+userName)) sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "pki"})) vaultStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) credService, err := credentiallibraries.NewService(ctx, iamRepoFn, vaultCredRepoFn, 1000) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Library Name"), Description: wrapperspb.String("Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: &wrapperspb.StringValue{ Value: path.Join("pki/{{ .User.Name}}", "issue", "boundary"), }, HttpMethod: &wrapperspb.StringValue{ Value: "POST", }, HttpRequestBody: &wrapperspb.StringValue{ Value: `{"common_name":"boundary.com", "alt_names": "{{.User.Name}},{{.Account.Name}},{{.Account.LoginName}},{{ truncateFrom .Account.LoginName "@" }}"}`, }, }, }, }}) require.NoError(t, err) const defaultPort = 2 cases := []struct { name string hostSourceId string credSourceId string wantedHostId string wantedEndpoint string wantedConnectionLimit int32 }{ { name: "static host", hostSourceId: shs.GetPublicId(), credSourceId: clsResp.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: fmt.Sprintf("%s:%d", h.GetAddress(), defaultPort), wantedConnectionLimit: -1, }, { name: "static host with port defined", hostSourceId: shsWithPort.GetPublicId(), credSourceId: clsResp.GetItem().GetId(), wantedHostId: hWithPort.GetPublicId(), wantedEndpoint: fmt.Sprintf("%s:%d", hWithPortBareAddress, defaultPort), wantedConnectionLimit: 10, }, { name: "plugin host", hostSourceId: phs.GetPublicId(), credSourceId: clsResp.GetItem().GetId(), wantedHostId: "?", wantedEndpoint: fmt.Sprintf("10.0.0.1:%d", defaultPort), wantedConnectionLimit: 100, }, } workerRPCGracePeriod := new(atomic.Int64) workerRPCGracePeriod.Store(int64(server.DefaultLiveness)) s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, workerRPCGracePeriod, 1000, nil) require.NoError(t, err) for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { // Generate correlation Id and add it to the context corId, err := uuid.GenerateUUID() require.NoError(t, err) ctx, err = event.NewCorrelationIdContext(ctx, corId) require.NoError(t, err) tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name, target.WithDefaultPort(defaultPort), target.WithSessionConnectionLimit(tc.wantedConnectionLimit)) apiTar, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{tc.hostSourceId}, }) require.NoError(t, err) _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()}, Version: apiTar.GetItem().GetVersion(), }) require.NoError(t, err) // Tell our DB that there is a worker ready to serve the data server.TestKmsWorker(t, conn, wrapper) asRes1, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ Id: tar.GetPublicId(), }) require.NoError(t, err) asRes2, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ Id: tar.GetPublicId(), }) require.NoError(t, err) assert.NotEmpty(t, cmp.Diff( asRes1.GetItem().GetCredentials(), asRes2.GetItem().GetCredentials(), protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), ), "the credentials aren't unique per request authorized session") _, err = s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ Id: tar.GetPublicId(), HostId: asRes2.GetItem().GetHostId(), }) require.NoError(t, err, "session must authorize with explicit host ID") wantedHostId := tc.wantedHostId if tc.wantedHostId == "?" { wantedHostId = asRes2.GetItem().GetHostId() } want := &pb.SessionAuthorization{ Scope: &scopes.ScopeInfo{ Id: proj.GetPublicId(), Type: proj.GetType(), Name: proj.GetName(), Description: proj.GetDescription(), ParentScopeId: proj.GetParentId(), }, TargetId: tar.GetPublicId(), UserId: at.GetIamUserId(), HostSetId: tc.hostSourceId, HostId: wantedHostId, Type: "tcp", Endpoint: fmt.Sprintf("tcp://%s", tc.wantedEndpoint), Credentials: []*pb.SessionCredential{ { CredentialSource: &pb.CredentialSource{ Id: clsResp.GetItem().GetId(), Name: clsResp.GetItem().GetName().GetValue(), Description: clsResp.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), }, }, }, EndpointPort: uint32(defaultPort), ConnectionLimit: tc.wantedConnectionLimit, // TODO: validate the contents of the authorization token is what is expected } wantSecret := map[string]any{ "certificate": "-----BEGIN CERTIFICATE-----\n", "issuing_ca": "-----BEGIN CERTIFICATE-----\n", "private_key": "-----BEGIN RSA PRIVATE KEY-----\n", "private_key_type": "rsa", } got := asRes1.GetItem() require.Len(t, got.GetCredentials(), 1) gotCred := got.Credentials[0] require.NotNil(t, gotCred.Secret) assert.NotEmpty(t, gotCred.Secret.Raw) dSec := decodeJsonSecret(t, gotCred.Secret.Raw) require.NoError(t, err) require.Equal(t, dSec, gotCred.Secret.Decoded.AsMap()) for k, v := range wantSecret { gotV, ok := dSec[k] require.True(t, ok) assert.Truef(t, strings.HasPrefix(gotV.(string), v.(string)), "%q:%q doesn't have prefix %q", k, gotV, v) } b, _ := pem.Decode([]byte(dSec["certificate"].(string))) require.NotNil(t, b) cert, err := x509.ParseCertificate(b.Bytes) require.NoError(t, err) assert.Contains(t, cert.DNSNames, userName) assert.Contains(t, cert.DNSNames, accountName) assert.Contains(t, cert.DNSNames, strings.Split(loginName, "@")[0]) assert.Contains(t, cert.EmailAddresses, loginName) gotCred.Secret = nil // CorrelationId is not part of the authSession resp, query the session directly sessRepo, err := sessionRepoFn() require.NoError(t, err) gotSess, _, err := sessRepo.LookupSession(ctx, got.SessionId) require.NoError(t, err) require.Equal(t, corId, gotSess.CorrelationId) got.AuthorizationToken, got.SessionId, got.CreatedTime = "", "", nil assert.Empty(t, cmp.Diff( got, want, protocmp.Transform(), protocmp.IgnoreFields(&pb.SessionAuthorization{}, "expiration"), cmpopts.SortSlices(func(a, b string) bool { return a < b }), )) }) } } func TestAuthorizeSessionTypedCredentials(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) // This prevents us from running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) sche := scheduler.TestScheduler(t, conn, wrapper) err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) { return session.NewRepository(ctx, rw, rw, kms, opts...) } staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(ctx, rw, rw, kms) } vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } staticCredRepoFn := func() (*credstatic.Repository, error) { return credstatic.NewRepository(ctx, rw, rw, kms) } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } pluginHostRepoFn := func() (*hostplugin.Repository, error) { return hostplugin.NewRepository(ctx, rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) } targetAliasRepoFn := func() (*talias.Repository, error) { return talias.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) ctx = auth.NewVerifierContext(requests.NewRequestContext(ctx), iamRepoFn, atRepoFn, serversRepoFn, kms, &authpb.RequestInfo{ Token: at.GetToken(), TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") workerRPCGracePeriod := new(atomic.Int64) workerRPCGracePeriod.Store(int64(server.DefaultLiveness)) s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, workerRPCGracePeriod, 1000, nil) require.NoError(t, err) hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] shs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, shs.GetPublicId(), []*static.Host{h}) v := vault.NewTestVaultServer(t) v.AddKVPolicy(t) sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "boundary-controller", "secret"})) vaultStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) credLibService, err := credentiallibraries.NewService(ctx, iamRepoFn, vaultCredRepoFn, 1000) require.NoError(t, err) // Create secret in vault with default username and password fields defaultUserPass := v.CreateKVSecret(t, "default-userpass", []byte(`{"data": {"username": "my-user", "password": "my-pass"}}`)) require.NotNil(t, defaultUserPass) clsRespUsernamePassword, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Usernamepassword Library"), Description: wrapperspb.String("Usernamepassword Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "username_password", }}) require.NoError(t, err) clsRespUnspecified, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Unspecified Library"), Description: wrapperspb.String("Unspecified Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-userpass")), HttpMethod: wrapperspb.String("GET"), }, }, }}) require.NoError(t, err) // Create secret in vault with non default username and password fields nonDefaultUserPass := v.CreateKVSecret(t, "non-default-userpass", []byte(`{"data": {"non-default-user": "my-user", "non-default-pass": "my-pass"}}`)) require.NotNil(t, nonDefaultUserPass) clsRespUsernamePasswordWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("Usernamepassword Mapping Library"), Description: wrapperspb.String("Usernamepassword Mapping Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "non-default-userpass")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "username_password", CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("non-default-user"), "password_attribute": structpb.NewStringValue("non-default-pass"), }}, }}) require.NoError(t, err) staticStore := credstatic.TestCredentialStore(t, conn, wrapper, proj.GetPublicId()) credService, err := credentials.NewService(ctx, iamRepoFn, staticCredRepoFn, 1000) require.NoError(t, err) upCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ CredentialStoreId: staticStore.GetPublicId(), Type: credential.UsernamePasswordSubtype.String(), Name: wrapperspb.String("Cred Name"), Description: wrapperspb.String("Cred Description"), Attrs: &credpb.Credential_UsernamePasswordAttributes{ UsernamePasswordAttributes: &credpb.UsernamePasswordAttributes{ Username: wrapperspb.String("static-username"), Password: wrapperspb.String("static-password"), }, }, }}) require.NoError(t, err) require.NotNil(t, upCredResp) sshPkCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ CredentialStoreId: staticStore.GetPublicId(), Type: credential.SshPrivateKeySubtype.String(), Name: wrapperspb.String("Cred SSH Name"), Description: wrapperspb.String("Cred SSH Description"), Attrs: &credpb.Credential_SshPrivateKeyAttributes{ SshPrivateKeyAttributes: &credpb.SshPrivateKeyAttributes{ Username: wrapperspb.String("static-username"), PrivateKey: wrapperspb.String(string(testdata.PEMBytes["ed25519"])), }, }, }}) require.NoError(t, err) require.NotNil(t, sshPkCredResp) sshPkWithPassCredResp, err := credService.CreateCredential(ctx, &pbs.CreateCredentialRequest{Item: &credpb.Credential{ CredentialStoreId: staticStore.GetPublicId(), Type: credential.SshPrivateKeySubtype.String(), Name: wrapperspb.String("Cred SSH With Pass Name"), Description: wrapperspb.String("Cred SSH Description"), Attrs: &credpb.Credential_SshPrivateKeyAttributes{ SshPrivateKeyAttributes: &credpb.SshPrivateKeyAttributes{ Username: wrapperspb.String("static-username"), PrivateKey: wrapperspb.String(string(testdata.PEMEncryptedKeys[0].PEMBytes)), PrivateKeyPassphrase: wrapperspb.String(testdata.PEMEncryptedKeys[0].EncryptionKey), }, }, }}) require.NoError(t, err) require.NotNil(t, sshPkWithPassCredResp) // Create secret in vault with default username and private key fields defaultSshPrivateKey := v.CreateKVSecret(t, "default-sshpk", []byte(`{"data": {"username": "my-user", "private_key": "my-pk"}}`)) require.NotNil(t, defaultSshPrivateKey) clsRespSshPrivateKey, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("SSH Private Key Library"), Description: wrapperspb.String("SSH Private Key Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-sshpk")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "ssh_private_key", }}) require.NoError(t, err) // Create secret in vault with non default username and private key fields nonDefaultSshPrivateKey := v.CreateKVSecret(t, "non-default-sshpk", []byte(`{"data": {"non-default-user": "my-user", "non-default-pk": "my-special-pk"}}`)) require.NotNil(t, nonDefaultSshPrivateKey) clsRespSshPrivateKeyWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("SSH Private Key Mapping Library"), Description: wrapperspb.String("SSH Private Key Mapping Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "non-default-sshpk")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "ssh_private_key", CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("non-default-user"), "private_key_attribute": structpb.NewStringValue("non-default-pk"), }}, }}) require.NoError(t, err) // Create secret in vault with default username, private key and passphrase fields defaultSshPrivateKeyWithPass := v.CreateKVSecret(t, "default-sshpk-with-pass", []byte(`{"data": {"username": "my-user", "private_key": "my-pk", "private_key_passphrase": "my-pass"}}`)) require.NotNil(t, defaultSshPrivateKeyWithPass) clsRespSshPrivateKeyWithPass, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("SSH Private Key With Passphrase Library"), Description: wrapperspb.String("SSH Private Key Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "default-sshpk-with-pass")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "ssh_private_key", }}) require.NoError(t, err) require.NotNil(t, clsRespSshPrivateKeyWithPass) // Create secret in vault with non default username, private key and private key passphrase fields nonDefaultSshPrivateKeyWithPass := v.CreateKVSecret(t, "non-default-sshpk-with-pass", []byte(`{"data": {"non-default-user": "my-user", "non-default-pk": "my-special-pk", "embedded": {"secret-pass": "my-special-pass"}}}}`)) require.NotNil(t, nonDefaultSshPrivateKeyWithPass) clsRespSshPrivateKeyWithPassWithMapping, err := credLibService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: vaultStore.GetPublicId(), Name: wrapperspb.String("SSH Private Key With Passphrase Mapping Library"), Description: wrapperspb.String("SSH Private Key Mapping Library Description"), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("secret", "data", "non-default-sshpk-with-pass")), HttpMethod: wrapperspb.String("GET"), }, }, CredentialType: "ssh_private_key", CredentialMappingOverrides: &structpb.Struct{Fields: map[string]*structpb.Value{ "username_attribute": structpb.NewStringValue("/data/non-default-user"), "private_key_attribute": structpb.NewStringValue("/data/non-default-pk"), "private_key_passphrase_attribute": structpb.NewStringValue("/data/embedded/secret-pass"), }}, }}) require.NoError(t, err) require.NotNil(t, clsRespSshPrivateKeyWithPassWithMapping) cases := []struct { name string hostSourceId string credSourceId string wantedHostId string wantedEndpoint string wantedCred *pb.SessionCredential wantedConnectionLimit int32 }{ { name: "vault-unspecified", hostSourceId: shs.GetPublicId(), credSourceId: clsRespUnspecified.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespUnspecified.GetItem().GetId(), Name: clsRespUnspecified.GetItem().GetName().GetValue(), Description: clsRespUnspecified.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), }, }, wantedConnectionLimit: 1, }, { name: "vault-usernamepassword", hostSourceId: shs.GetPublicId(), credSourceId: clsRespUsernamePassword.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespUsernamePassword.GetItem().GetId(), Name: clsRespUsernamePassword.GetItem().GetName().GetValue(), Description: clsRespUsernamePassword.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.UsernamePasswordCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "password": "my-pass", "username": "my-user", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 10, }, { name: "vault-UsernamePassword-with-mapping", hostSourceId: shs.GetPublicId(), credSourceId: clsRespUsernamePasswordWithMapping.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespUsernamePasswordWithMapping.GetItem().GetId(), Name: clsRespUsernamePasswordWithMapping.GetItem().GetName().GetValue(), Description: clsRespUsernamePasswordWithMapping.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.UsernamePasswordCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "password": "my-pass", "username": "my-user", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 100, }, { name: "static-UsernamePassword", hostSourceId: shs.GetPublicId(), credSourceId: upCredResp.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: upCredResp.GetItem().GetId(), Name: upCredResp.GetItem().GetName().GetValue(), Description: upCredResp.GetItem().GetDescription().GetValue(), CredentialStoreId: staticStore.GetPublicId(), Type: credstatic.Subtype.String(), CredentialType: string(globals.UsernamePasswordCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "password": "static-password", "username": "static-username", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 1000, }, { name: "static-ssh-private-key", hostSourceId: shs.GetPublicId(), credSourceId: sshPkCredResp.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: sshPkCredResp.GetItem().GetId(), Name: sshPkCredResp.GetItem().GetName().GetValue(), Description: sshPkCredResp.GetItem().GetDescription().GetValue(), CredentialStoreId: staticStore.GetPublicId(), Type: credstatic.Subtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "private_key": string(testdata.PEMBytes["ed25519"]), "username": "static-username", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 10000, }, { name: "vault-ssh-private-key", hostSourceId: shs.GetPublicId(), credSourceId: clsRespSshPrivateKey.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespSshPrivateKey.GetItem().GetId(), Name: clsRespSshPrivateKey.GetItem().GetName().GetValue(), Description: clsRespSshPrivateKey.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "private_key": "my-pk", "username": "my-user", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 50000, }, { name: "vault-ssh-private-key-with-mapping", hostSourceId: shs.GetPublicId(), credSourceId: clsRespSshPrivateKeyWithMapping.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespSshPrivateKeyWithMapping.GetItem().GetId(), Name: clsRespSshPrivateKeyWithMapping.GetItem().GetName().GetValue(), Description: clsRespSshPrivateKeyWithMapping.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "username": "my-user", "private_key": "my-special-pk", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 100000, }, { name: "static-ssh-private-key-with-pass", hostSourceId: shs.GetPublicId(), credSourceId: sshPkWithPassCredResp.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: sshPkWithPassCredResp.GetItem().GetId(), Name: sshPkWithPassCredResp.GetItem().GetName().GetValue(), Description: sshPkWithPassCredResp.GetItem().GetDescription().GetValue(), CredentialStoreId: staticStore.GetPublicId(), Type: credstatic.Subtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "private_key_passphrase": testdata.PEMEncryptedKeys[0].EncryptionKey, "private_key": string(testdata.PEMEncryptedKeys[0].PEMBytes), "username": "static-username", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 500000, }, { name: "vault-ssh-private-key-with-pass", hostSourceId: shs.GetPublicId(), credSourceId: clsRespSshPrivateKeyWithPass.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespSshPrivateKeyWithPass.GetItem().GetId(), Name: clsRespSshPrivateKeyWithPass.GetItem().GetName().GetValue(), Description: clsRespSshPrivateKeyWithPass.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "username": "my-user", "private_key": "my-pk", "private_key_passphrase": "my-pass", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: 1000000, }, { name: "vault-ssh-private-key-with-pass-with-mapping", hostSourceId: shs.GetPublicId(), credSourceId: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetId(), wantedHostId: h.GetPublicId(), wantedEndpoint: h.GetAddress(), wantedCred: &pb.SessionCredential{ CredentialSource: &pb.CredentialSource{ Id: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetId(), Name: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetName().GetValue(), Description: clsRespSshPrivateKeyWithPassWithMapping.GetItem().GetDescription().GetValue(), CredentialStoreId: vaultStore.GetPublicId(), Type: vault.GenericLibrarySubtype.String(), CredentialType: string(globals.SshPrivateKeyCredentialType), }, Credential: func() *structpb.Struct { data := map[string]any{ "username": "my-user", "private_key": "my-special-pk", "private_key_passphrase": "my-special-pass", } st, err := structpb.NewStruct(data) require.NoError(t, err) return st }(), }, wantedConnectionLimit: -1, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { const defaultPort = 2 tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name, target.WithDefaultPort(defaultPort), target.WithSessionConnectionLimit(tc.wantedConnectionLimit)) apiTar, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{tc.hostSourceId}, }) require.NoError(t, err) _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), BrokeredCredentialSourceIds: []string{tc.credSourceId}, Version: apiTar.GetItem().GetVersion(), }) require.NoError(t, err) // Tell our DB that there is a worker ready to serve the data server.TestKmsWorker(t, conn, wrapper) // Generate correlation Id and add it to the context corId, err := uuid.GenerateUUID() require.NoError(t, err) ctx, err = event.NewCorrelationIdContext(ctx, corId) require.NoError(t, err) asRes, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ Id: tar.GetPublicId(), }) require.NoError(t, err) want := &pb.SessionAuthorization{ Scope: &scopes.ScopeInfo{ Id: proj.GetPublicId(), Type: proj.GetType(), Name: proj.GetName(), Description: proj.GetDescription(), ParentScopeId: proj.GetParentId(), }, TargetId: tar.GetPublicId(), UserId: at.GetIamUserId(), HostSetId: tc.hostSourceId, HostId: tc.wantedHostId, Type: "tcp", Endpoint: fmt.Sprintf("tcp://%s:%d", tc.wantedEndpoint, defaultPort), Credentials: []*pb.SessionCredential{tc.wantedCred}, EndpointPort: uint32(defaultPort), Expiration: asRes.Item.Expiration, ConnectionLimit: tc.wantedConnectionLimit, // TODO: validate the contents of the authorization token is what is expected } got := asRes.GetItem() require.Len(t, got.GetCredentials(), 1) gotCred := got.Credentials[0] require.NotNil(t, gotCred.Secret) assert.NotEmpty(t, gotCred.Secret.Raw) gotCred.Secret = nil got.AuthorizationToken, got.SessionId, got.CreatedTime = "", "", nil assert.Empty(t, cmp.Diff( got, want, protocmp.Transform(), cmpopts.SortSlices(func(a, b string) bool { return a < b }), )) }) } } func TestAuthorizeSession_Errors(t *testing.T) { ctx := context.Background() // This prevents us from running tests in parallel. server.TestUseCommunityFilterWorkersFn(t) conn, _ := db.TestSetup(t, "postgres") rw := db.New(conn) wrapper := db.TestWrapper(t) kms := kms.TestKms(t, conn, wrapper) sche := scheduler.TestScheduler(t, conn, wrapper) err := vault.RegisterJobs(context.Background(), sche, rw, rw, kms) require.NoError(t, err) repoFn := func(o ...target.Option) (*target.Repository, error) { return target.NewRepository(ctx, rw, rw, kms) } iamRepo := iam.TestRepo(t, conn, wrapper) iamRepoFn := func() (*iam.Repository, error) { return iamRepo, nil } serversRepoFn := func() (*server.Repository, error) { return server.NewRepository(ctx, rw, rw, kms) } sessionRepoFn := func(opts ...session.Option) (*session.Repository, error) { return session.NewRepository(ctx, rw, rw, kms, opts...) } staticHostRepoFn := func() (*static.Repository, error) { return static.NewRepository(ctx, rw, rw, kms) } pluginHostRepoFn := func() (*hostplugin.Repository, error) { return hostplugin.NewRepository(ctx, rw, rw, kms, sche, map[string]plgpb.HostPluginServiceClient{}) } vaultCredRepoFn := func() (*vault.Repository, error) { return vault.NewRepository(ctx, rw, rw, kms, sche) } staticCredRepoFn := func() (*credstatic.Repository, error) { return credstatic.NewRepository(ctx, rw, rw, kms) } atRepoFn := func() (*authtoken.Repository, error) { return authtoken.NewRepository(ctx, rw, rw, kms) } targetAliasRepoFn := func() (*talias.Repository, error) { return talias.NewRepository(ctx, rw, rw, kms) } org, proj := iam.TestScopes(t, iamRepo) workerRPCGracePeriod := new(atomic.Int64) workerRPCGracePeriod.Store(int64(server.DefaultLiveness)) s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, workerRPCGracePeriod, 1000, nil) require.NoError(t, err) // Authorized user gets full permissions at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId()) ctx = auth.NewVerifierContext(requests.NewRequestContext(context.Background()), iamRepoFn, atRepoFn, serversRepoFn, kms, &authpb.RequestInfo{ Token: at.GetToken(), TokenFormat: uint32(auth.AuthTokenTypeBearer), PublicId: at.GetPublicId(), }) r := iam.TestRole(t, conn, proj.GetPublicId()) _ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId()) _ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "ids=*;type=*;actions=*") v := vault.NewTestVaultServer(t, vault.WithDockerNetwork(true)) v.MountDatabase(t) sec, tok := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"})) store := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok, sec.Auth.Accessor) sec1, tok1 := v.CreateToken(t, vault.WithPolicies([]string{"default", "database"})) expiredStore := vault.TestCredentialStore(t, conn, wrapper, proj.GetPublicId(), v.Addr, tok1, sec1.Auth.Accessor) // Set previous token to expired in the database and revoke in Vault to validate a // credential store with an expired token is correctly returned over the API num, err := rw.Exec(context.Background(), "update credential_vault_token set status = ? where store_id = ?", []any{vault.ExpiredToken, expiredStore.PublicId}) require.NoError(t, err) assert.Equal(t, 1, num) v.RevokeToken(t, tok1) workerExists := func(tar target.Target) target.Target { server.TestKmsWorker(t, conn, wrapper) return tar } hostSetNoHostExists := func(tar target.Target) target.Target { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{hs.GetPublicId()}, }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } hostExists := func(tar target.Target) target.Target { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) _, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{hs.GetPublicId()}, }) require.NoError(t, err) hostRepo, err := staticHostRepoFn() require.NoError(t, err) _, _, err = hostRepo.UpdateHost(ctx, hc.GetProjectId(), h, h.GetVersion(), []string{"address"}) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } hostWithoutPort := func(tar target.Target) target.Target { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHosts(t, conn, hc.GetPublicId(), 1)[0] hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) _, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{hs.GetPublicId()}, }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } ipv4HostWithHostPort := func(tar target.Target) target.Target { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHost(t, conn, hc.GetPublicId(), static.WithAddress("8.8.8.8:22")) hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) _, err := s.SetTargetHostSources(ctx, &pbs.SetTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{hs.GetPublicId()}, }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } ipv6HostWithHostPort := func(tar target.Target) target.Target { hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0] h := static.TestHost(t, conn, hc.GetPublicId(), static.WithAddress("[2001:4860:4860:0:0:0:0:8888]:22")) hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0] _ = static.TestSetMembers(t, conn, hs.GetPublicId(), []*static.Host{h}) _, err := s.SetTargetHostSources(ctx, &pbs.SetTargetHostSourcesRequest{ Id: tar.GetPublicId(), Version: tar.GetVersion(), HostSourceIds: []string{hs.GetPublicId()}, }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } libraryExists := func(tar target.Target) target.Target { credService, err := credentiallibraries.NewService(ctx, iamRepoFn, vaultCredRepoFn, 1000) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("database", "creds", "opened")), }, }, }}) require.NoError(t, err) _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()}, Version: tar.GetVersion(), }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } misConfiguredlibraryExists := func(tar target.Target) target.Target { credService, err := credentiallibraries.NewService(ctx, iamRepoFn, vaultCredRepoFn, 1000) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: store.GetPublicId(), Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String("bad path"), }, }, }}) require.NoError(t, err) _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()}, Version: tar.GetVersion(), }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } expiredTokenLibrary := func(tar target.Target) target.Target { credService, err := credentiallibraries.NewService(ctx, iamRepoFn, vaultCredRepoFn, 1000) require.NoError(t, err) clsResp, err := credService.CreateCredentialLibrary(ctx, &pbs.CreateCredentialLibraryRequest{Item: &credlibpb.CredentialLibrary{ CredentialStoreId: expiredStore.GetPublicId(), Description: wrapperspb.String(fmt.Sprintf("Library Description for target %q", tar.GetName())), Type: vault.GenericLibrarySubtype.String(), Attrs: &credlibpb.CredentialLibrary_VaultCredentialLibraryAttributes{ VaultCredentialLibraryAttributes: &credlibpb.VaultCredentialLibraryAttributes{ Path: wrapperspb.String(path.Join("database", "creds", "opened")), }, }, }}) require.NoError(t, err) _, err = s.AddTargetCredentialSources(ctx, &pbs.AddTargetCredentialSourcesRequest{ Id: tar.GetPublicId(), BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()}, Version: tar.GetVersion(), }) require.NoError(t, err) repo, err := repoFn() require.NoError(t, err) tar, err = repo.LookupTarget(ctx, tar.GetPublicId()) require.NoError(t, err) return tar } // Generate correlation Id and add it to the context corId, err := uuid.GenerateUUID() require.NoError(t, err) ctxWithCor, err := event.NewCorrelationIdContext(ctx, corId) require.NoError(t, err) cases := []struct { name string ctx context.Context setup []func(target.Target) target.Target useTargetId bool wantErr bool wantErrContains string }{ { // This one must be run first since it relies on the DB not having any worker details name: "no worker", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{hostExists, libraryExists}, useTargetId: true, wantErrContains: "No workers are available to handle this session", }, { name: "success", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostExists, libraryExists}, useTargetId: true, }, { name: "no target", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostExists, libraryExists}, useTargetId: false, wantErrContains: "Resource not found", }, { name: "no host port", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostWithoutPort, libraryExists}, useTargetId: true, }, { name: "ipv4 target address host port", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{ workerExists, func(tcpTarget target.Target) target.Target { repo, err := repoFn() require.NoError(t, err) n, err := repo.DeleteTarget(ctx, tcpTarget.GetPublicId()) require.NoError(t, err) assert.Equal(t, 1, n) return tcp.TestTarget(ctx, t, conn, tcpTarget.GetProjectId(), tcpTarget.GetName(), target.WithAddress("127.0.0.1:22"), target.WithDefaultPort(22)) }, }, wantErrContains: "error when parsing the chosen endpoint host address: unknown: error #0: address contains a port", useTargetId: true, }, { name: "ipv6 target address host port", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{ workerExists, func(tcpTarget target.Target) target.Target { repo, err := repoFn() require.NoError(t, err) n, err := repo.DeleteTarget(ctx, tcpTarget.GetPublicId()) require.NoError(t, err) assert.Equal(t, 1, n) return tcp.TestTarget(ctx, t, conn, tcpTarget.GetProjectId(), tcpTarget.GetName(), target.WithAddress("[2001:4860:4860:0:0:0:0:8888]:22"), target.WithDefaultPort(22)) }, }, wantErrContains: "error when parsing the chosen endpoint host address: unknown: error #0: address contains a port", useTargetId: true, }, { name: "ipv4 static host port", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{ipv4HostWithHostPort}, wantErrContains: "error when parsing the chosen endpoint host address: unknown: error #0: address contains a port", useTargetId: true, }, { name: "ipv6 static host port", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{ipv6HostWithHostPort}, wantErrContains: "error when parsing the chosen endpoint host address: unknown: error #0: address contains a port", useTargetId: true, }, { name: "no hosts", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostSetNoHostExists, libraryExists}, useTargetId: true, wantErrContains: "No host sources or address found for given target", }, { name: "bad library configuration", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostExists, misConfiguredlibraryExists}, useTargetId: true, wantErrContains: "external system issue: error #3014: Error making API request", }, { name: "expired token library", ctx: ctxWithCor, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostExists, expiredTokenLibrary}, useTargetId: true, wantErrContains: "vault.newClient: invalid configuration", }, { name: "no correaltion id", ctx: ctx, setup: []func(tcpTarget target.Target) target.Target{workerExists, hostExists, libraryExists}, useTargetId: true, wantErrContains: "authorize session: missing correlation id", }, } for i, tc := range cases { t.Run(tc.name, func(t *testing.T) { ctx := tc.ctx tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), fmt.Sprintf("test-%d", i), target.WithDefaultPort(22)) for _, fn := range tc.setup { tar = fn(tar) } id := tar.GetPublicId() if !tc.useTargetId { id = "ttcp_bogusid" } res, err := s.AuthorizeSession(ctx, &pbs.AuthorizeSessionRequest{ Id: id, }) if tc.wantErrContains != "" { require.Error(t, err) require.Nil(t, res) require.ErrorContains(t, err, tc.wantErrContains) return } require.NoError(t, err) require.NotNil(t, res) }) } } func decodeJsonSecret(t *testing.T, in string) map[string]any { t.Helper() ret := make(map[string]any) dec := json.NewDecoder(base64.NewDecoder(base64.StdEncoding, strings.NewReader(in))) require.NoError(t, dec.Decode(&ret)) return ret }