Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to test gateway opening/closing in Connect #14135

Merged
merged 11 commits into from
Jul 7, 2022
1 change: 1 addition & 0 deletions lib/srv/alpnproxy/local_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func createAWSAccessProxySuite(t *testing.T, cred *credentials.Credentials) *Loc
t.Cleanup(func() {
err := lp.Close()
require.NoError(t, err)
hs.Close()
})
go func() {
err := lp.StartAWSAccessProxy(context.Background())
Expand Down
24 changes: 18 additions & 6 deletions lib/teleterm/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ func (s *Service) createGateway(ctx context.Context, params CreateGatewayParams)
return nil, trace.Wrap(err)
}

gateway.Open()
go func() {
if err := gateway.Serve(); err != nil {
gateway.Log.WithError(err).Warn("Failed to open a connection.")
}
}()

s.gateways = append(s.gateways, gateway)

Expand Down Expand Up @@ -202,22 +206,28 @@ func (s *Service) RemoveGateway(ctx context.Context, gatewayURI string) error {
s.mu.Lock()
defer s.mu.Unlock()

s.removeGateway(gateway)
if err := s.removeGateway(gateway); err != nil {
return trace.Wrap(err)
}

return nil
}

// removeGateway assumes that mu is already held by a public method.
func (s *Service) removeGateway(gateway *gateway.Gateway) {
gateway.Close()
func (s *Service) removeGateway(gateway *gateway.Gateway) error {
if err := gateway.Close(); err != nil {
espadolini marked this conversation as resolved.
Show resolved Hide resolved
return trace.Wrap(err)
}

// remove closed gateway from list
for index := range s.gateways {
if s.gateways[index] == gateway {
s.gateways = append(s.gateways[:index], s.gateways[index+1:]...)
espadolini marked this conversation as resolved.
Show resolved Hide resolved
return
return nil
}
}

return trace.NotFound("gateway %v not found in gateway list", gateway.URI.String())
}

// RestartGateway stops a gateway and starts a new one with identical parameters.
Expand All @@ -232,7 +242,9 @@ func (s *Service) RestartGateway(ctx context.Context, gatewayURI string) error {
s.mu.Lock()
defer s.mu.Unlock()

s.removeGateway(gateway)
if err := s.removeGateway(gateway); err != nil {
return trace.Wrap(err)
}

newGateway, err := s.createGateway(ctx, CreateGatewayParams{
TargetURI: gateway.TargetURI,
Expand Down
28 changes: 17 additions & 11 deletions lib/teleterm/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,21 +104,27 @@ func New(cfg Config, cliCommandProvider CLICommandProvider) (*Gateway, error) {
}

// Close terminates gateway connection
func (g *Gateway) Close() {
func (g *Gateway) Close() error {
g.closeCancel()
g.localProxy.Close()

if err := g.localProxy.Close(); err != nil {
return trace.Wrap(err)
}

return nil
}

// Open opens a gateway to Teleport proxy
func (g *Gateway) Open() {
go func() {
g.Log.Info("Gateway is open.")
if err := g.localProxy.Start(g.closeContext); err != nil {
g.Log.WithError(err).Warn("Failed to open a connection.")
}
// Serve starts the underlying ALPN proxy. Blocks until closeContext is canceled.
func (g *Gateway) Serve() error {
g.Log.Info("Gateway is open.")

g.Log.Info("Gateway has closed.")
}()
if err := g.localProxy.Start(g.closeContext); err != nil {
return trace.Wrap(err)
}

g.Log.Info("Gateway has closed.")

return nil
}

// LocalPortInt returns the port of a gateway as an integer rather than a string.
Expand Down
64 changes: 64 additions & 0 deletions lib/teleterm/gateway/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,16 @@ package gateway

import (
"fmt"
"net"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/teleterm/api/uri"

"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
)

Expand All @@ -45,3 +51,61 @@ func TestCLICommandUsesCLICommandProvider(t *testing.T) {

require.Equal(t, "foo/bar", command)
}

func TestGatewayStart(t *testing.T) {
hs := httptest.NewTLSServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {}))
t.Cleanup(func() {
hs.Close()
})

gateway, err := New(
Config{
TargetName: "foo",
TargetURI: uri.NewClusterURI("bar").AppendDB("foo").String(),
TargetUser: "alice",
Protocol: defaults.ProtocolPostgres,
CertPath: "../../../fixtures/certs/proxy1.pem",
KeyPath: "../../../fixtures/certs/proxy1-key.pem",
Insecure: true,
WebProxyAddr: hs.Listener.Addr().String(),
},
mockCLICommandProvider{},
)
require.NoError(t, err)
t.Cleanup(func() { gateway.Close() })
gatewayAddress := net.JoinHostPort(gateway.LocalAddress, gateway.LocalPort)

serveErr := make(chan error)

go func() {
err := gateway.Serve()
serveErr <- err
}()

blockUntilGatewayAcceptsConnections(t, gatewayAddress)

err = gateway.Close()
require.NoError(t, err)
require.NoError(t, <-serveErr)
}

func blockUntilGatewayAcceptsConnections(t *testing.T, address string) {
conn, err := net.DialTimeout("tcp", address, time.Second*1)
require.NoError(t, err)
t.Cleanup(func() { conn.Close() })

err = conn.SetReadDeadline(time.Now().Add(time.Second))
require.NoError(t, err)

out := make([]byte, 1024)
_, err = conn.Read(out)
// Our "client" here is going to fail the handshake because it requests an application protocol
// (typically teleport-<some db protocol>) that the target server (typically
// httptest.NewTLSServer) doesn't support.
//
// So we just expect EOF here. In case of a timeout, this check will fail.
require.True(t, trace.IsEOF(err), "expected EOF, got %v", err)

err = conn.Close()
require.NoError(t, err)
}