Files
threshold/internal/daemon/controller/handlers/targets/tcp/target_service_test.go
T
Jeff Mitchell 62d4f8453b Don't explode grants from the DB (#5104)
This removes the Cartesian product in the DB for GrantsForUser in favor
of returning the actual grant scope information and dealing with it in
application code.

(cherry picked from commit 913cd536823d73994acc4f4d833208c1cc05f91b)
2024-09-24 14:18:53 +00:00

4660 lines
172 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) (version uint32) {
server.TestKmsWorker(t, conn, wrapper)
return tar.GetVersion()
}
hostSetNoHostExists := func(tar target.Target) (version uint32) {
hc := static.TestCatalogs(t, conn, proj.GetPublicId(), 1)[0]
hs := static.TestSets(t, conn, hc.GetPublicId(), 1)[0]
tr, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{
Id: tar.GetPublicId(),
Version: tar.GetVersion(),
HostSourceIds: []string{hs.GetPublicId()},
})
require.NoError(t, err)
return tr.GetItem().GetVersion()
}
hostExists := func(tar target.Target) (version uint32) {
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})
apiTar, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{
Id: tar.GetPublicId(),
Version: tar.GetVersion(),
HostSourceIds: []string{hs.GetPublicId()},
})
require.NoError(t, err)
repo, err := staticHostRepoFn()
require.NoError(t, err)
_, _, err = repo.UpdateHost(ctx, hc.GetProjectId(), h, h.GetVersion(), []string{"address"})
require.NoError(t, err)
return apiTar.GetItem().GetVersion()
}
hostWithoutPort := func(tar target.Target) (version uint32) {
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})
apiTar, err := s.AddTargetHostSources(ctx, &pbs.AddTargetHostSourcesRequest{
Id: tar.GetPublicId(),
Version: tar.GetVersion(),
HostSourceIds: []string{hs.GetPublicId()},
})
require.NoError(t, err)
return apiTar.GetItem().GetVersion()
}
libraryExists := func(tar target.Target) (version uint32) {
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)
tr, err := s.AddTargetCredentialSources(ctx,
&pbs.AddTargetCredentialSourcesRequest{
Id: tar.GetPublicId(),
BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()},
Version: tar.GetVersion(),
})
require.NoError(t, err)
return tr.GetItem().GetVersion()
}
misConfiguredlibraryExists := func(tar target.Target) (version uint32) {
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)
tr, err := s.AddTargetCredentialSources(ctx,
&pbs.AddTargetCredentialSourcesRequest{
Id: tar.GetPublicId(),
BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()},
Version: tar.GetVersion(),
})
require.NoError(t, err)
return tr.GetItem().GetVersion()
}
expiredTokenLibrary := func(tar target.Target) (version uint32) {
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)
tr, err := s.AddTargetCredentialSources(ctx,
&pbs.AddTargetCredentialSourcesRequest{
Id: tar.GetPublicId(),
BrokeredCredentialSourceIds: []string{clsResp.GetItem().GetId()},
Version: tar.GetVersion(),
})
require.NoError(t, err)
return tr.GetItem().GetVersion()
}
// 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) uint32
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) uint32{hostExists, libraryExists},
useTargetId: true,
wantErrContains: "No workers are available to handle this session",
},
{
name: "success",
ctx: ctxWithCor,
setup: []func(tcpTarget target.Target) uint32{workerExists, hostExists, libraryExists},
useTargetId: true,
},
{
name: "no target",
ctx: ctxWithCor,
setup: []func(tcpTarget target.Target) uint32{workerExists, hostExists, libraryExists},
useTargetId: false,
wantErrContains: "Resource not found",
},
{
name: "no host port",
ctx: ctxWithCor,
setup: []func(tcpTarget target.Target) uint32{workerExists, hostWithoutPort, libraryExists},
useTargetId: true,
},
{
name: "host port",
ctx: ctxWithCor,
setup: []func(tcpTarget target.Target) uint32{
workerExists, func(tcpTarget target.Target) uint32 {
tcpTarget.SetAddress("127.0.0.1:22")
repo, err := repoFn()
require.NoError(t, err)
tcpTarget, _, err = repo.UpdateTarget(ctx, tcpTarget, tcpTarget.GetVersion(), []string{"address"})
require.NoError(t, err)
return tcpTarget.GetVersion()
},
},
wantErrContains: "Address specified for use unexpectedly contains a port",
useTargetId: true,
},
{
name: "no hosts",
ctx: ctxWithCor,
setup: []func(tcpTarget target.Target) uint32{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) uint32{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) uint32{workerExists, hostExists, expiredTokenLibrary},
useTargetId: true,
wantErrContains: "vault.newClient: invalid configuration",
},
{
name: "no correaltion id",
ctx: ctx,
setup: []func(tcpTarget target.Target) uint32{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 {
ver := fn(tar)
tar.SetVersion(ver)
}
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
}