Files
threshold/internal/daemon/controller/handlers/targets/tcp/target_service_test.go
T

4748 lines
175 KiB
Go

// 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)
statusGracePeriod := new(atomic.Int64)
statusGracePeriod.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, statusGracePeriod, 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) {
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, "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
workerFilterFn := targets.AuthorizeSessionWorkerFilterFn
targets.AuthorizeSessionWorkerFilterFn = targets.AuthorizeSessionWithWorkerFilter
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)
})
}
// Reset worker filter func
targets.AuthorizeSessionWorkerFilterFn = workerFilterFn
}
func TestCreate_AliasAuthCheck(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())
// 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
workerFilterFn := targets.AuthorizeSessionWorkerFilterFn
targets.AuthorizeSessionWorkerFilterFn = targets.AuthorizeSessionWithWorkerFilter
t.Cleanup(func() {
targets.AuthorizeSessionWorkerFilterFn = workerFilterFn
})
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) {
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.")
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
workerFilterFn := targets.AuthorizeSessionWorkerFilterFn
targets.AuthorizeSessionWorkerFilterFn = targets.AuthorizeSessionWithWorkerFilter
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.AuthorizeSessionWorkerFilterFn = workerFilterFn
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.
targets.SetupSuiteTargetFilters(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
}{
{
name: "static host",
hostSourceId: shs.GetPublicId(),
credSourceId: clsResp.GetItem().GetId(),
wantedHostId: h.GetPublicId(),
wantedEndpoint: fmt.Sprintf("%s:%d", h.GetAddress(), defaultPort),
},
{
name: "static host with port defined",
hostSourceId: shsWithPort.GetPublicId(),
credSourceId: clsResp.GetItem().GetId(),
wantedHostId: hWithPort.GetPublicId(),
wantedEndpoint: fmt.Sprintf("%s:%d", hWithPortBareAddress, defaultPort),
},
{
name: "plugin host",
hostSourceId: phs.GetPublicId(),
credSourceId: clsResp.GetItem().GetId(),
wantedHostId: "?",
wantedEndpoint: fmt.Sprintf("10.0.0.1:%d", defaultPort),
},
}
statusGracePeriod := new(atomic.Int64)
statusGracePeriod.Store(int64(server.DefaultLiveness))
s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, statusGracePeriod, 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))
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),
// 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.
targets.SetupSuiteTargetFilters(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=*")
statusGracePeriod := new(atomic.Int64)
statusGracePeriod.Store(int64(server.DefaultLiveness))
s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, statusGracePeriod, 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
}{
{
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(),
},
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
{
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
}(),
},
},
}
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))
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,
// 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.
targets.SetupSuiteTargetFilters(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)
statusGracePeriod := new(atomic.Int64)
statusGracePeriod.Store(int64(server.DefaultLiveness))
s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, vaultCredRepoFn, staticCredRepoFn, targetAliasRepoFn, nil, statusGracePeriod, 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
}