Require default ports to be defined for newly created tcp targets. (#2148)
This commit is contained in:
12 files changed
+282
-81
No files matched your search
@@ -17,9 +17,15 @@ type Attributes interface {
|
||||
|
||||
// Options create target.Options to be used to create a target.Target.
|
||||
Options() []target.Option
|
||||
|
||||
// Vet validates the Attributes and returns a map of fields to error messages
|
||||
// if any fields are invalid.
|
||||
Vet() map[string]string
|
||||
|
||||
// VetForUpdate validates the Attributes for an updated resource with the
|
||||
// provided mask paths and returns a map of fields to error messages if any
|
||||
// fields are invalid.
|
||||
VetForUpdate([]string) map[string]string
|
||||
}
|
||||
|
||||
type attributeFunc func(interface{}) Attributes
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/globals"
|
||||
@@ -47,8 +49,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
credentialDomain = "credential"
|
||||
hostDomain = "host"
|
||||
credentialDomain = "credential"
|
||||
hostDomain = "host"
|
||||
missingPortErrStr = "missing port in address"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -1028,15 +1031,21 @@ func (s Service) AuthorizeSession(ctx context.Context, req *pbs.AuthorizeSession
|
||||
chosenEndpoint = endpoints[rand.Intn(len(endpoints))]
|
||||
}
|
||||
|
||||
h, p, err := net.SplitHostPort(chosenEndpoint.Address)
|
||||
switch {
|
||||
case err != nil && strings.Contains(err.Error(), missingPortErrStr):
|
||||
if t.GetDefaultPort() == 0 {
|
||||
return nil, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("neither the selected host %q nor the target provides a port to use", chosenEndpoint.HostId))
|
||||
}
|
||||
h = chosenEndpoint.Address
|
||||
p = strconv.FormatUint(uint64(t.GetDefaultPort()), 10)
|
||||
case err != nil:
|
||||
return nil, errors.Wrap(ctx, err, op, errors.WithMsg("error when parsing the chosen endpoints host address"))
|
||||
}
|
||||
// Generate the endpoint URL
|
||||
endpointUrl := &url.URL{
|
||||
Scheme: t.GetType().String(),
|
||||
}
|
||||
defaultPort := t.GetDefaultPort()
|
||||
if defaultPort != 0 {
|
||||
endpointUrl.Host = fmt.Sprintf("%s:%d", chosenEndpoint.Address, defaultPort)
|
||||
} else {
|
||||
endpointUrl.Host = chosenEndpoint.Address
|
||||
Host: net.JoinHostPort(h, p),
|
||||
}
|
||||
|
||||
var reqs []credential.Request
|
||||
@@ -1752,7 +1761,8 @@ func validateCreateRequest(req *pbs.CreateTargetRequest) error {
|
||||
func validateUpdateRequest(req *pbs.UpdateTargetRequest) error {
|
||||
return handlers.ValidateUpdateRequest(req, req.GetItem(), func() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
if handlers.MaskContains(req.GetUpdateMask().GetPaths(), globals.NameField) && req.GetItem().GetName().GetValue() == "" {
|
||||
paths := req.GetUpdateMask().GetPaths()
|
||||
if handlers.MaskContains(paths, globals.NameField) && req.GetItem().GetName().GetValue() == "" {
|
||||
badFields[globals.NameField] = "This field cannot be set to empty."
|
||||
}
|
||||
if req.GetItem().GetSessionConnectionLimit() != nil {
|
||||
@@ -1785,7 +1795,7 @@ func validateUpdateRequest(req *pbs.UpdateTargetRequest) error {
|
||||
if err != nil {
|
||||
badFields[globals.AttributesField] = "Attribute fields do not match the expected format."
|
||||
} else {
|
||||
for k, v := range a.Vet() {
|
||||
for k, v := range a.VetForUpdate(paths) {
|
||||
badFields[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,6 +474,17 @@ func TestCreate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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(),
|
||||
WorkerFilter: wrapperspb.String(`type == "bar"`),
|
||||
}},
|
||||
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
|
||||
},
|
||||
{
|
||||
name: "Create with default port 0",
|
||||
req: &pbs.CreateTargetRequest{Item: &pb.Target{
|
||||
@@ -604,24 +615,25 @@ func TestUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
ttar, err := target.New(ctx, tcp.Subtype, proj.GetPublicId(), target.WithName("default"), target.WithDescription("default"))
|
||||
ttar, err := target.New(ctx, tcp.Subtype, proj.GetPublicId(),
|
||||
target.WithName("default"),
|
||||
target.WithDescription("default"),
|
||||
target.WithSessionMaxSeconds(1),
|
||||
target.WithSessionConnectionLimit(1),
|
||||
target.WithDefaultPort(2))
|
||||
require.NoError(t, err)
|
||||
tar := ttar.(*tcp.Target)
|
||||
tar.DefaultPort = 2
|
||||
tar, _, _, err := repo.CreateTarget(context.Background(), ttar)
|
||||
require.NoError(t, err)
|
||||
gtar, _, _, err := repo.CreateTarget(context.Background(), tar)
|
||||
tar, _, _, err = repo.AddTargetHostSources(context.Background(), tar.GetPublicId(), tar.GetVersion(), []string{hs[0].GetPublicId(), hs[1].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
gtar, _, _, err = repo.AddTargetHostSources(context.Background(), gtar.GetPublicId(), gtar.GetVersion(), []string{hs[0].GetPublicId(), hs[1].GetPublicId()})
|
||||
require.NoError(t, err)
|
||||
tar = gtar.(*tcp.Target)
|
||||
|
||||
var version uint32 = gtar.GetVersion()
|
||||
|
||||
resetTarget := func() {
|
||||
version++
|
||||
_, _, _, _, err = repo.UpdateTarget(context.Background(), tar, version, []string{"Name", "Description"})
|
||||
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"})
|
||||
require.NoError(t, err, "Failed to reset target.")
|
||||
version++
|
||||
}
|
||||
|
||||
hCreated := tar.GetCreateTime().GetTimestamp().AsTime()
|
||||
@@ -706,8 +718,8 @@ func TestUpdate(t *testing.T) {
|
||||
HostSets: hostSets,
|
||||
HostSourceIds: hostSourceIds,
|
||||
HostSources: hostSources,
|
||||
SessionMaxSeconds: wrapperspb.UInt32(3600),
|
||||
SessionConnectionLimit: wrapperspb.Int32(5),
|
||||
SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()),
|
||||
SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
@@ -736,7 +748,7 @@ func TestUpdate(t *testing.T) {
|
||||
{
|
||||
name: "Update port to 0",
|
||||
req: &pbs.UpdateTargetRequest{
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{"default_port"}},
|
||||
UpdateMask: &field_mask.FieldMask{Paths: []string{"attributes.default_port"}},
|
||||
Item: &pb.Target{
|
||||
Attrs: &pb.Target_TcpTargetAttributes{
|
||||
TcpTargetAttributes: &pb.TcpTargetAttributes{
|
||||
@@ -747,6 +759,14 @@ func TestUpdate(t *testing.T) {
|
||||
},
|
||||
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-existant paths in Mask",
|
||||
req: &pbs.UpdateTargetRequest{
|
||||
@@ -797,8 +817,8 @@ func TestUpdate(t *testing.T) {
|
||||
HostSets: hostSets,
|
||||
HostSourceIds: hostSourceIds,
|
||||
HostSources: hostSources,
|
||||
SessionMaxSeconds: wrapperspb.UInt32(3600),
|
||||
SessionConnectionLimit: wrapperspb.Int32(5),
|
||||
SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()),
|
||||
SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
@@ -832,8 +852,8 @@ func TestUpdate(t *testing.T) {
|
||||
HostSets: hostSets,
|
||||
HostSourceIds: hostSourceIds,
|
||||
HostSources: hostSources,
|
||||
SessionMaxSeconds: wrapperspb.UInt32(3600),
|
||||
SessionConnectionLimit: wrapperspb.Int32(5),
|
||||
SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()),
|
||||
SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
@@ -867,8 +887,8 @@ func TestUpdate(t *testing.T) {
|
||||
HostSets: hostSets,
|
||||
HostSourceIds: hostSourceIds,
|
||||
HostSources: hostSources,
|
||||
SessionMaxSeconds: wrapperspb.UInt32(3600),
|
||||
SessionConnectionLimit: wrapperspb.Int32(5),
|
||||
SessionMaxSeconds: wrapperspb.UInt32(tar.GetSessionMaxSeconds()),
|
||||
SessionConnectionLimit: wrapperspb.Int32(tar.GetSessionConnectionLimit()),
|
||||
AuthorizedActions: testAuthorizedActions,
|
||||
},
|
||||
},
|
||||
@@ -934,8 +954,9 @@ func TestUpdate(t *testing.T) {
|
||||
}
|
||||
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 = version
|
||||
tc.req.Item.Version = tar.GetVersion()
|
||||
|
||||
req := proto.Clone(toMerge).(*pbs.UpdateTargetRequest)
|
||||
proto.Merge(req, tc.req)
|
||||
@@ -944,11 +965,9 @@ func TestUpdate(t *testing.T) {
|
||||
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
|
||||
}
|
||||
|
||||
if tc.err == nil {
|
||||
defer resetTarget()
|
||||
}
|
||||
require.NoError(gErr)
|
||||
|
||||
if got != nil {
|
||||
assert.NotNilf(tc.res, "Expected UpdateHost response to be nil, but was %v", got)
|
||||
@@ -961,7 +980,7 @@ func TestUpdate(t *testing.T) {
|
||||
got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil
|
||||
}
|
||||
if tc.res != nil {
|
||||
tc.res.Item.Version = version + 1
|
||||
tc.res.Item.Version = tc.req.Item.Version + 1
|
||||
}
|
||||
assert.Empty(cmp.Diff(got, tc.res, protocmp.Transform()), "UpdateTarget(%q) got response %q, wanted %q", req, got, tc.res)
|
||||
})
|
||||
@@ -2649,8 +2668,10 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
connectionRepoFn := func() (*session.ConnectionRepository, error) {
|
||||
return session.NewConnectionRepository(ctx, rw, rw, kms)
|
||||
}
|
||||
staticRepo, err := static.NewRepository(rw, rw, kms)
|
||||
require.NoError(t, err)
|
||||
staticHostRepoFn := func() (*static.Repository, error) {
|
||||
return static.NewRepository(rw, rw, kms)
|
||||
return staticRepo, nil
|
||||
}
|
||||
credentialRepoFn := func() (*vault.Repository, error) {
|
||||
return vault.NewRepository(rw, rw, kms, sche)
|
||||
@@ -2705,14 +2726,19 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
_ = iam.TestUserRole(t, conn, r.GetPublicId(), at.GetIamUserId())
|
||||
_ = iam.TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*")
|
||||
|
||||
s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn)
|
||||
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})
|
||||
|
||||
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})
|
||||
hWithPort.Address = fmt.Sprintf("%s:54321", hWithPort.GetAddress())
|
||||
hWithPort, _, err = staticRepo.UpdateHost(ctx, hcWithPort.GetScopeId(), hWithPort, hWithPort.GetVersion(), []string{"address"})
|
||||
require.NoError(t, err)
|
||||
|
||||
phc := plugin.TestCatalog(t, conn, proj.GetPublicId(), plg.GetPublicId())
|
||||
phs := plugin.TestSet(t, conn, kms, sche, phc, plgm, plugin.WithPreferredEndpoints([]string{"cidr:10.0.0.0/24"}))
|
||||
|
||||
@@ -2746,6 +2772,7 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
const defaultPort = 2
|
||||
cases := []struct {
|
||||
name string
|
||||
hostSourceId string
|
||||
@@ -2758,20 +2785,29 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
hostSourceId: shs.GetPublicId(),
|
||||
credSourceId: clsResp.GetItem().GetId(),
|
||||
wantedHostId: h.GetPublicId(),
|
||||
wantedEndpoint: h.GetAddress(),
|
||||
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: hWithPort.GetAddress(),
|
||||
},
|
||||
{
|
||||
name: "plugin host",
|
||||
hostSourceId: phs.GetPublicId(),
|
||||
credSourceId: clsResp.GetItem().GetId(),
|
||||
wantedHostId: "?",
|
||||
wantedEndpoint: "10.0.0.1",
|
||||
wantedEndpoint: fmt.Sprintf("10.0.0.1:%d", defaultPort),
|
||||
},
|
||||
}
|
||||
|
||||
s, err := targets.NewService(ctx, kms, repoFn, iamRepoFn, serversRepoFn, sessionRepoFn, pluginHostRepoFn, staticHostRepoFn, credentialRepoFn)
|
||||
require.NoError(t, err)
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name)
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name, target.WithDefaultPort(defaultPort))
|
||||
apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{
|
||||
Id: tar.GetPublicId(),
|
||||
Version: tar.GetVersion(),
|
||||
@@ -3059,7 +3095,8 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name)
|
||||
const defaultPort = 2
|
||||
tar := tcp.TestTarget(ctx, t, conn, proj.GetPublicId(), tc.name, target.WithDefaultPort(defaultPort))
|
||||
apiTar, err := s.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{
|
||||
Id: tar.GetPublicId(),
|
||||
Version: tar.GetVersion(),
|
||||
@@ -3102,7 +3139,7 @@ func TestAuthorizeSessionTypedCredentials(t *testing.T) {
|
||||
HostSetId: tc.hostSourceId,
|
||||
HostId: tc.wantedHostId,
|
||||
Type: "tcp",
|
||||
Endpoint: fmt.Sprintf("tcp://%s", tc.wantedEndpoint),
|
||||
Endpoint: fmt.Sprintf("tcp://%s:%d", tc.wantedEndpoint, defaultPort),
|
||||
Credentials: []*pb.SessionCredential{
|
||||
{
|
||||
CredentialLibrary: &pb.CredentialLibrary{
|
||||
@@ -3231,6 +3268,25 @@ func TestAuthorizeSession_Errors(t *testing.T) {
|
||||
}
|
||||
|
||||
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.AddTargetHostSets(ctx, &pbs.AddTargetHostSetsRequest{
|
||||
Id: tar.GetPublicId(),
|
||||
Version: tar.GetVersion(),
|
||||
HostSetIds: []string{hs.GetPublicId()},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
h.Address = fmt.Sprintf("%s:54321", h.GetAddress())
|
||||
repo, err := staticHostRepoFn()
|
||||
require.NoError(t, err)
|
||||
h, _, err = repo.UpdateHost(ctx, hc.GetScopeId(), 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]
|
||||
@@ -3307,6 +3363,11 @@ func TestAuthorizeSession_Errors(t *testing.T) {
|
||||
name: "success",
|
||||
setup: []func(tcpTarget target.Target) uint32{workerExists, hostExists, libraryExists},
|
||||
},
|
||||
{
|
||||
name: "no port",
|
||||
setup: []func(tcpTarget target.Target) uint32{workerExists, hostWithoutPort, libraryExists},
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "no hosts",
|
||||
setup: []func(tcpTarget target.Target) uint32{workerExists, hostSetNoHostExists, libraryExists},
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/targets"
|
||||
)
|
||||
|
||||
const defaultPortField = "attributes.default_port"
|
||||
|
||||
type attribute struct {
|
||||
*pb.TcpTargetAttributes
|
||||
}
|
||||
@@ -24,8 +26,23 @@ func (a *attribute) Options() []target.Option {
|
||||
|
||||
func (a *attribute) Vet() map[string]string {
|
||||
badFields := map[string]string{}
|
||||
if a.GetDefaultPort() != nil && a.GetDefaultPort().GetValue() == 0 {
|
||||
badFields["attributes.default_port"] = "This optional field cannot be set to 0."
|
||||
if a.GetDefaultPort() == nil {
|
||||
badFields["attributes.default_port"] = "This field is required."
|
||||
} else if a.GetDefaultPort().GetValue() == 0 {
|
||||
badFields["attributes.default_port"] = "This field cannot be set to zero."
|
||||
}
|
||||
return badFields
|
||||
}
|
||||
|
||||
func (a *attribute) VetForUpdate(p []string) map[string]string {
|
||||
if !handlers.MaskContains(p, defaultPortField) {
|
||||
return nil
|
||||
}
|
||||
badFields := map[string]string{}
|
||||
if a.GetDefaultPort() == nil {
|
||||
badFields["attributes.default_port"] = "This field is required."
|
||||
} else if a.GetDefaultPort().GetValue() == 0 {
|
||||
badFields["attributes.default_port"] = "This cannot be set to zero."
|
||||
}
|
||||
return badFields
|
||||
}
|
||||
|
||||
+40
-15
@@ -21,16 +21,36 @@ type AllocFunc func() Target
|
||||
// be used by the Repository.
|
||||
type VetFunc func(context.Context, Target) error
|
||||
|
||||
// VetForUpdateFunc is a function that checks the given Target and field mask
|
||||
// paths are valid and be used to update a target in the Repository.
|
||||
type VetForUpdateFunc func(context.Context, Target, []string) error
|
||||
|
||||
// VetCredentialLibrariesFunc is a function that checks the given CredentialLibraries
|
||||
// to ensure that they are valid for a Target subtype.
|
||||
type VetCredentialLibrariesFunc func(context.Context, []*CredentialLibrary) error
|
||||
|
||||
// targetHooks defines the interface containing all the hooks needed for
|
||||
// managing target suptypes.
|
||||
type targetHooks interface {
|
||||
// NewTarget creates a new in memory target.
|
||||
NewTarget(scopeId string, opt ...Option) (Target, error)
|
||||
// AllocTarget will allocate an empty target.
|
||||
AllocTarget() Target
|
||||
// Vet validates that the given Target has the proper fields and values
|
||||
// for creation in the database for this type of target.
|
||||
Vet(ctx context.Context, t Target) error
|
||||
// VetForUpdate validates that the given Target has the proper fields
|
||||
// and values for updating the database for this type of target given the
|
||||
// field mask paths.
|
||||
VetForUpdate(ctx context.Context, t Target, paths []string) error
|
||||
// VetCredentialLibraries checks that the provided credential
|
||||
// libriaries are properly built for association with a target of this type.
|
||||
VetCredentialLibraries(ctx context.Context, cls []*CredentialLibrary) error
|
||||
}
|
||||
|
||||
type registryEntry struct {
|
||||
newFunc NewFunc
|
||||
alloc AllocFunc
|
||||
vet VetFunc
|
||||
vetCredentialLibraries VetCredentialLibrariesFunc
|
||||
prefix string
|
||||
targetHooks targetHooks
|
||||
prefix string
|
||||
}
|
||||
|
||||
type registry struct {
|
||||
@@ -71,7 +91,7 @@ func (r *registry) newFunc(s subtypes.Subtype) (NewFunc, bool) {
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
return entry.newFunc, ok
|
||||
return entry.targetHooks.NewTarget, ok
|
||||
}
|
||||
|
||||
func (r *registry) allocFunc(s subtypes.Subtype) (AllocFunc, bool) {
|
||||
@@ -79,7 +99,7 @@ func (r *registry) allocFunc(s subtypes.Subtype) (AllocFunc, bool) {
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
return entry.alloc, ok
|
||||
return entry.targetHooks.AllocTarget, ok
|
||||
}
|
||||
|
||||
func (r *registry) vetFunc(s subtypes.Subtype) (VetFunc, bool) {
|
||||
@@ -87,7 +107,15 @@ func (r *registry) vetFunc(s subtypes.Subtype) (VetFunc, bool) {
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
return entry.vet, ok
|
||||
return entry.targetHooks.Vet, ok
|
||||
}
|
||||
|
||||
func (r *registry) vetForUpdateFunc(s subtypes.Subtype) (VetForUpdateFunc, bool) {
|
||||
entry, ok := r.get(s)
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
return entry.targetHooks.VetForUpdate, ok
|
||||
}
|
||||
|
||||
func (r *registry) vetCredentialLibrariesFunc(s subtypes.Subtype) (VetCredentialLibrariesFunc, bool) {
|
||||
@@ -95,7 +123,7 @@ func (r *registry) vetCredentialLibrariesFunc(s subtypes.Subtype) (VetCredential
|
||||
if !ok {
|
||||
return nil, ok
|
||||
}
|
||||
return entry.vetCredentialLibraries, ok
|
||||
return entry.targetHooks.VetCredentialLibraries, ok
|
||||
}
|
||||
|
||||
func (r *registry) idPrefix(s subtypes.Subtype) (string, bool) {
|
||||
@@ -140,12 +168,9 @@ func New(ctx context.Context, subtype subtypes.Subtype, scopeId string, opt ...O
|
||||
// Register registers repository hooks and the prefixes for a provided Subtype. Register
|
||||
// panics if the subtype has already been registered or if any of the
|
||||
// prefixes are associated with another subtype.
|
||||
func Register(s subtypes.Subtype, nf NewFunc, af AllocFunc, vf VetFunc, vclf VetCredentialLibrariesFunc, prefix string) {
|
||||
func Register(s subtypes.Subtype, th targetHooks, prefix string) {
|
||||
subtypeRegistry.set(s, ®istryEntry{
|
||||
newFunc: nf,
|
||||
alloc: af,
|
||||
vet: vf,
|
||||
vetCredentialLibraries: vclf,
|
||||
prefix: prefix,
|
||||
targetHooks: th,
|
||||
prefix: prefix,
|
||||
})
|
||||
}
|
||||
@@ -493,11 +493,11 @@ func (r *Repository) UpdateTarget(ctx context.Context, target Target, version ui
|
||||
if target == nil {
|
||||
return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, "missing target")
|
||||
}
|
||||
vet, ok := subtypeRegistry.vetFunc(target.GetType())
|
||||
vet, ok := subtypeRegistry.vetForUpdateFunc(target.GetType())
|
||||
if !ok {
|
||||
return nil, nil, nil, db.NoRowsAffected, errors.New(ctx, errors.InvalidParameter, op, fmt.Sprintf("unsupported target type %s", target.GetType()))
|
||||
}
|
||||
if err := vet(ctx, target); err != nil {
|
||||
if err := vet(ctx, target, fieldMaskPaths); err != nil {
|
||||
return nil, nil, nil, db.NoRowsAffected, err
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,30 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type hooks struct{}
|
||||
|
||||
func (h hooks) NewTarget(scopeId string, opt ...target.Option) (target.Target, error) {
|
||||
return targettest.New(scopeId, opt...)
|
||||
}
|
||||
|
||||
func (h hooks) AllocTarget() target.Target {
|
||||
return targettest.Alloc()
|
||||
}
|
||||
|
||||
func (h hooks) Vet(ctx context.Context, t target.Target) error {
|
||||
return targettest.Vet(ctx, t)
|
||||
}
|
||||
|
||||
func (h hooks) VetForUpdate(ctx context.Context, t target.Target, paths []string) error {
|
||||
return targettest.VetForUpdate(ctx, t, paths)
|
||||
}
|
||||
|
||||
func (h hooks) VetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error {
|
||||
return targettest.VetCredentialLibraries(ctx, cls)
|
||||
}
|
||||
|
||||
func TestRepository_SetTargetCredentialSources(t *testing.T) {
|
||||
target.Register(targettest.Subtype, targettest.New, targettest.Alloc, targettest.Vet, targettest.VetCredentialLibraries, targettest.TargetPrefix)
|
||||
target.Register(targettest.Subtype, hooks{}, targettest.TargetPrefix)
|
||||
|
||||
t.Parallel()
|
||||
conn, _ := db.TestSetup(t, "postgres")
|
||||
|
||||
@@ -5,6 +5,7 @@ package targettest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/db"
|
||||
@@ -203,6 +204,32 @@ func Vet(ctx context.Context, t target.Target) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// vet validates that the given Target is a targettest.Target and that it
|
||||
// has a Target store.
|
||||
func VetForUpdate(ctx context.Context, t target.Target, paths []string) error {
|
||||
const op = "targettest.vetForUpdate"
|
||||
|
||||
tt, ok := t.(*Target)
|
||||
if !ok {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "target is not a tcp.Target")
|
||||
}
|
||||
|
||||
switch {
|
||||
case tt == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target")
|
||||
case tt.Target == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target store")
|
||||
}
|
||||
|
||||
for _, f := range paths {
|
||||
if strings.EqualFold("defaultport", f) && tt.GetDefaultPort() == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "clearing or setting default port to zero")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VetCredentialLibraries allows for any CredentialLibraries.
|
||||
func VetCredentialLibraries(_ context.Context, _ []*target.CredentialLibrary) error {
|
||||
return nil
|
||||
|
||||
@@ -13,7 +13,7 @@ var (
|
||||
// performed by NewTarget, allowing tests to create Targets with
|
||||
// nil scopeIds for more robust testing.
|
||||
func NewTestTarget(scopeId string, opt ...target.Option) target.Target {
|
||||
t, _ := newTarget("testScope", opt...)
|
||||
t, _ := targetHooks{}.NewTarget("testScope", opt...)
|
||||
t.SetScopeId(scopeId)
|
||||
return t
|
||||
}
|
||||
@@ -3,14 +3,17 @@ package tcp
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/credential"
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/target"
|
||||
)
|
||||
|
||||
type targetHooks struct{}
|
||||
|
||||
func init() {
|
||||
target.Register(Subtype, newTarget, allocTarget, vet, vetCredentialLibraries, TargetPrefix)
|
||||
target.Register(Subtype, targetHooks{}, TargetPrefix)
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -18,9 +21,9 @@ const (
|
||||
TargetPrefix = "ttcp"
|
||||
)
|
||||
|
||||
// vet validates that the given target.Target is a tcp.Target and that it
|
||||
// Vet validates that the given target.Target is a tcp.Target and that it
|
||||
// has a Target store.
|
||||
func vet(ctx context.Context, t target.Target) error {
|
||||
func (h targetHooks) Vet(ctx context.Context, t target.Target) error {
|
||||
const op = "tcp.vet"
|
||||
|
||||
tt, ok := t.(*Target)
|
||||
@@ -35,12 +38,42 @@ func vet(ctx context.Context, t target.Target) error {
|
||||
if tt.Target == nil {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target store")
|
||||
}
|
||||
if tt.GetDefaultPort() == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target default port")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// vetCredentialLibraries checks that all of the provided credential libriaries have a CredentialPurpose
|
||||
// VetForUpdate validates that the given target.Target is a tcp.Target,
|
||||
// and that it has a Target store and that it isn't attempting to clear or
|
||||
// set to zero the default port.
|
||||
func (h targetHooks) VetForUpdate(ctx context.Context, t target.Target, paths []string) error {
|
||||
const op = "tcp.vetForUpdate"
|
||||
|
||||
tt, ok := t.(*Target)
|
||||
if !ok {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "target is not a tcp.Target")
|
||||
}
|
||||
|
||||
switch {
|
||||
case tt == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target")
|
||||
case tt.Target == nil:
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "missing target store")
|
||||
}
|
||||
|
||||
for _, f := range paths {
|
||||
if strings.EqualFold("defaultport", f) && tt.GetDefaultPort() == 0 {
|
||||
return errors.New(ctx, errors.InvalidParameter, op, "clearing or setting default port to zero")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VetCredentialLibraries checks that all of the provided credential libriaries have a CredentialPurpose
|
||||
// of ApplicationPurpose. Any other CredentialPurpose will result in an error.
|
||||
func vetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error {
|
||||
func (h targetHooks) VetCredentialLibraries(ctx context.Context, cls []*target.CredentialLibrary) error {
|
||||
const op = "tcp.vetCredentialLibraries"
|
||||
|
||||
for _, cl := range cls {
|
||||
|
||||
@@ -37,9 +37,9 @@ var (
|
||||
_ oplog.ReplayableMessage = (*Target)(nil)
|
||||
)
|
||||
|
||||
// newTarget creates a new in memory tcp target. WithName, WithDescription and
|
||||
// NewTarget creates a new in memory tcp target. WithName, WithDescription and
|
||||
// WithDefaultPort options are supported
|
||||
func newTarget(scopeId string, opt ...target.Option) (target.Target, error) {
|
||||
func (h targetHooks) NewTarget(scopeId string, opt ...target.Option) (target.Target, error) {
|
||||
const op = "tcp.NewTarget"
|
||||
opts := target.GetOpts(opt...)
|
||||
if scopeId == "" {
|
||||
@@ -59,8 +59,8 @@ func newTarget(scopeId string, opt ...target.Option) (target.Target, error) {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// allocTarget will allocate a tcp target
|
||||
func allocTarget() target.Target {
|
||||
// AllocTarget will allocate a tcp target
|
||||
func (h targetHooks) AllocTarget() target.Target {
|
||||
return &Target{
|
||||
Target: &store.Target{},
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestHostSetASD(t *testing.T) {
|
||||
require.NotNil(hSet2)
|
||||
|
||||
tarClient := targets.NewClient(client)
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"))
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"), targets.WithTcpTargetDefaultPort(2))
|
||||
require.NoError(err)
|
||||
require.NotNil(tar)
|
||||
assert.Empty(tar.Item.HostSetIds)
|
||||
@@ -97,7 +97,7 @@ func TestCredentialLibraryASD(t *testing.T) {
|
||||
require.NotNil(r1)
|
||||
|
||||
tarClient := targets.NewClient(client)
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"))
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"), targets.WithTcpTargetDefaultPort(2))
|
||||
require.NoError(err)
|
||||
require.NotNil(tar)
|
||||
assert.Empty(tar.Item.ApplicationCredentialLibraryIds)
|
||||
@@ -160,7 +160,7 @@ func TestList(t *testing.T) {
|
||||
expected = append(expected, &targets.Target{Name: fmt.Sprint(i)})
|
||||
}
|
||||
|
||||
tcr, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName(expected[0].Name))
|
||||
tcr, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName(expected[0].Name), targets.WithTcpTargetDefaultPort(2))
|
||||
require.NoError(err)
|
||||
expected[0] = tcr.Item
|
||||
|
||||
@@ -237,7 +237,7 @@ func TestCrud(t *testing.T) {
|
||||
|
||||
tarClient := targets.NewClient(client)
|
||||
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"))
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"), targets.WithTcpTargetDefaultPort(2))
|
||||
checkResource(t, "create", tar.Item, err, "foo", 1)
|
||||
|
||||
tar, err = tarClient.Read(tc.Context(), tar.Item.Id)
|
||||
@@ -268,7 +268,7 @@ func TestSet_Errors(t *testing.T) {
|
||||
|
||||
tarClient := targets.NewClient(client)
|
||||
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"))
|
||||
tar, err := tarClient.Create(tc.Context(), "tcp", proj.GetPublicId(), targets.WithName("foo"), targets.WithTcpTargetDefaultPort(2))
|
||||
require.NoError(err)
|
||||
assert.NotNil(tar)
|
||||
|
||||
|
||||
Reference in new issue
Block a user