diff --git a/api/proxy/option.go b/api/proxy/option.go index d3aa24c5b0..3f37d89c33 100644 --- a/api/proxy/option.go +++ b/api/proxy/option.go @@ -9,6 +9,7 @@ import ( "net/netip" "time" + "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/targets" ) @@ -37,6 +38,7 @@ type Options struct { WithSessionAuthorizationData *targets.SessionAuthorizationData WithSkipSessionTeardown bool withSessionTeardownTimeout time.Duration + withApiClient *api.Client } // Option is a function that takes in an options struct and sets values or @@ -129,3 +131,14 @@ func WithSessionTeardownTimeout(with time.Duration) Option { return nil } } + +// WithApiClient provides an optional Boundary API client +// Experimental: It is unclear whether the current usage of this option is the +// approach that we want to take in the long term. This may be removed at any +// point going forward. +func WithApiClient(with *api.Client) Option { + return func(o *Options) error { + o.withApiClient = with + return nil + } +} diff --git a/api/proxy/option_test.go b/api/proxy/option_test.go index 6b7f0a0d9b..8525010507 100644 --- a/api/proxy/option_test.go +++ b/api/proxy/option_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/api" "github.com/hashicorp/boundary/api/targets" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -96,4 +97,15 @@ func Test_GetOpts(t *testing.T) { require.NoError(t, err) assert.Equal(3*time.Millisecond, opts.withSessionTeardownTimeout) }) + t.Run("withSessionsClient", func(t *testing.T) { + assert := assert.New(t) + opts, err := getOpts() + require.NoError(t, err) + assert.Nil(opts.withApiClient) + client, err := api.NewClient(nil) + require.NoError(t, err) + opts, err = getOpts(WithApiClient(client)) + require.NoError(t, err) + assert.Equal(client, opts.withApiClient) + }) } diff --git a/api/proxy/proxy.go b/api/proxy/proxy.go index 3e26da7f04..789dbfccf3 100644 --- a/api/proxy/proxy.go +++ b/api/proxy/proxy.go @@ -15,6 +15,8 @@ import ( "sync/atomic" "time" + "github.com/hashicorp/boundary/api" + "github.com/hashicorp/boundary/api/sessions" "github.com/hashicorp/boundary/api/targets" cleanhttp "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/go-secure-stdlib/base62" @@ -37,6 +39,7 @@ type ClientProxy struct { connectionsLeft *atomic.Int32 connsLeftCh chan int32 callerConnectionsLeftCh chan int32 + apiClient *api.Client sessionAuthzData *targets.SessionAuthorizationData createTime time.Time expiration time.Time @@ -97,6 +100,7 @@ func New(ctx context.Context, authzToken string, opt ...Option) (*ClientProxy, e callerConnectionsLeftCh: opts.WithConnectionsLeftCh, started: new(atomic.Bool), skipSessionTeardown: opts.WithSkipSessionTeardown, + apiClient: opts.withApiClient, } if opts.WithListener != nil { @@ -260,6 +264,27 @@ func (p *ClientProxy) Start(opt ...Option) (retErr error) { p.cancel() return } + + // TODO: Determine if this is useful or if there is a better approach + // that we may use in the long term. + if p.apiClient != nil { + // If we can tell that the session for the connection we just + // closed is terminated, we can close the listener, otherwise + // might as well leave it open so the next connection can be + // tried. + sess, err := sessions.NewClient(p.apiClient).Read(p.ctx, p.sessionAuthzData.SessionId) + if err != nil || sess == nil || sess.Item == nil || sess.Item.TerminationReason == "" { + return + } + + // We got a valid session response for the session we just + // closed a connection for. Since there is a termination reason + // we can treat the session as being terminated so no more + // connections will be able to be established. + fin <- fmt.Errorf("session no longer active") + listenerCloseFunc() + p.cancel() + } }() } }()