Skip to content

Commit

Permalink
Add basic support for target port in gateways in Connect
Browse files Browse the repository at this point in the history
* Update type for targetSubresourceName on DocumentGateway

The way DocumentsService.createGatewayDocument is implemented means that
the targetSubresourceName property is always present, but it can be undefined.

* Use "local port" instead of "port" in DocumentGatewapApp

* Rewrite gateway FieldInputs to use styled components

* Update comments in protos

* useGateway: Stabilize useAsync functions of ports

* Add padding to menu label if it's first child

* Add support for required prop to Input and FieldInput

* Add UI for changing target port

* ActionButtons: Show ports of multi-port apps when VNet is not supported

Now that we have support for the target port in Connect's gateways, we
can show the ports and then open a gateway for that specific port on
click.

* Add RWMutex to gateways

* Clear app gateway cert on target port change

* Remove gateways/app.LocalProxyURL

It was used only in tests and it made sense only for web apps anyway.

* TestTCP: Close connections when test ends

* Create context with timeout in testGatewayCertRenewal

…instead of in each function that uses it.

* Add tests for changing the target port of a TCP gateway

* Parallelize app gateway tests within MFA/non-MFA groups

* Make testGatewayConnection take ctx as first arg

This will be needed in tests that check target port validation.

* Validate target port of app gateways

* Increase timeouts in app gateway tests
  • Loading branch information
ravicious committed Jan 14, 2025
1 parent b806725 commit 1477004
Show file tree
Hide file tree
Showing 36 changed files with 904 additions and 233 deletions.
5 changes: 3 additions & 2 deletions gen/proto/go/teleport/lib/teleterm/v1/gateway.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions gen/proto/ts/teleport/lib/teleterm/v1/gateway_pb.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions integration/appaccess/appaccess_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,7 @@ func TestTCP(t *testing.T) {

conn, err := net.Dial("tcp", localProxyAddress)
require.NoError(t, err)
defer conn.Close()

buf := make([]byte, 1024)
n, err := conn.Read(buf)
Expand Down
56 changes: 56 additions & 0 deletions integration/appaccess/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,34 @@ func (p *Pack) RootAppPublicAddr() string {
return p.rootAppPublicAddr
}

func (p *Pack) RootTCPAppName() string {
return p.rootTCPAppName
}

func (p *Pack) RootTCPMessage() string {
return p.rootTCPMessage
}

func (p *Pack) RootTCPMultiPortAppName() string {
return p.rootTCPMultiPortAppName
}

func (p *Pack) RootTCPMultiPortAppPortAlpha() int {
return p.rootTCPMultiPortAppPortAlpha
}

func (p *Pack) RootTCPMultiPortMessageAlpha() string {
return p.rootTCPMultiPortMessageAlpha
}

func (p *Pack) RootTCPMultiPortAppPortBeta() int {
return p.rootTCPMultiPortAppPortBeta
}

func (p *Pack) RootTCPMultiPortMessageBeta() string {
return p.rootTCPMultiPortMessageBeta
}

func (p *Pack) RootAuthServer() *auth.Server {
return p.rootCluster.Process.GetAuthServer()
}
Expand All @@ -201,6 +229,34 @@ func (p *Pack) LeafAppPublicAddr() string {
return p.leafAppPublicAddr
}

func (p *Pack) LeafTCPAppName() string {
return p.leafTCPAppName
}

func (p *Pack) LeafTCPMessage() string {
return p.leafTCPMessage
}

func (p *Pack) LeafTCPMultiPortAppName() string {
return p.leafTCPMultiPortAppName
}

func (p *Pack) LeafTCPMultiPortAppPortAlpha() int {
return p.leafTCPMultiPortAppPortAlpha
}

func (p *Pack) LeafTCPMultiPortMessageAlpha() string {
return p.leafTCPMultiPortMessageAlpha
}

func (p *Pack) LeafTCPMultiPortAppPortBeta() int {
return p.leafTCPMultiPortAppPortBeta
}

func (p *Pack) LeafTCPMultiPortMessageBeta() string {
return p.leafTCPMultiPortMessageBeta
}

func (p *Pack) LeafAuthServer() *auth.Server {
return p.leafCluster.Process.GetAuthServer()
}
Expand Down
53 changes: 46 additions & 7 deletions integration/proxy/proxy_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -684,7 +685,7 @@ func mustFindKubePod(t *testing.T, tc *client.TeleportClient) {
require.Equal(t, types.KindKubePod, response.Resources[0].Kind)
}

func mustConnectDatabaseGateway(t *testing.T, _ *daemon.Service, gw gateway.Gateway) {
func mustConnectDatabaseGateway(ctx context.Context, t *testing.T, _ *daemon.Service, gw gateway.Gateway) {
t.Helper()

dbGateway, err := gateway.AsDatabase(gw)
Expand All @@ -705,15 +706,15 @@ func mustConnectDatabaseGateway(t *testing.T, _ *daemon.Service, gw gateway.Gate
require.NoError(t, client.Close())
}

// mustConnectAppGateway verifies that the gateway acts as an unauthenticated proxy that forwards
// requests to the app behind it.
func mustConnectAppGateway(t *testing.T, _ *daemon.Service, gw gateway.Gateway) {
// mustConnectWebAppGateway verifies that the gateway acts as an unauthenticated proxy that forwards
// requests to the web app behind it.
func mustConnectWebAppGateway(ctx context.Context, t *testing.T, _ *daemon.Service, gw gateway.Gateway) {
t.Helper()

appGw, err := gateway.AsApp(gw)
require.NoError(t, err)
gatewayAddress := net.JoinHostPort(gw.LocalAddress(), gw.LocalPort())
gatewayURL := fmt.Sprintf("http://%s", gatewayAddress)

req, err := http.NewRequest(http.MethodGet, appGw.LocalProxyURL(), nil)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, gatewayURL, nil)
require.NoError(t, err)

client := &http.Client{}
Expand All @@ -724,6 +725,44 @@ func mustConnectAppGateway(t *testing.T, _ *daemon.Service, gw gateway.Gateway)
require.Equal(t, http.StatusOK, resp.StatusCode)
}

func makeMustConnectMultiPortTCPAppGateway(wantMessage string, otherTargetPort int, otherWantMessage string) testGatewayConnectionFunc {
return func(ctx context.Context, t *testing.T, d *daemon.Service, gw gateway.Gateway) {
t.Helper()

gwURI := gw.URI().String()
originalTargetPort := gw.TargetSubresourceName()
makeMustConnectTCPAppGateway(wantMessage)(ctx, t, d, gw)

_, err := d.SetGatewayTargetSubresourceName(ctx, gwURI, strconv.Itoa(otherTargetPort))
require.NoError(t, err)
makeMustConnectTCPAppGateway(otherWantMessage)(ctx, t, d, gw)

// Restore the original port, so that the next time the test calls this function after certs
// expire, wantMessage is going to match the port that the gateway points to.
_, err = d.SetGatewayTargetSubresourceName(ctx, gwURI, originalTargetPort)
require.NoError(t, err)
makeMustConnectTCPAppGateway(wantMessage)(ctx, t, d, gw)
}
}

func makeMustConnectTCPAppGateway(wantMessage string) testGatewayConnectionFunc {
return func(ctx context.Context, t *testing.T, _ *daemon.Service, gw gateway.Gateway) {
t.Helper()

gatewayAddress := net.JoinHostPort(gw.LocalAddress(), gw.LocalPort())
conn, err := net.Dial("tcp", gatewayAddress)
require.NoError(t, err)
defer conn.Close()

buf := make([]byte, 1024)
n, err := conn.Read(buf)
require.NoError(t, err)

resp := strings.TrimSpace(string(buf[:n]))
require.Equal(t, wantMessage, resp)
}
}

func kubeClientForLocalProxy(t *testing.T, kubeconfigPath, teleportCluster, kubeCluster string) *kubernetes.Clientset {
t.Helper()

Expand Down
30 changes: 21 additions & 9 deletions integration/proxy/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
"github.com/gravitational/teleport/lib"
"github.com/gravitational/teleport/lib/auth/testauthority"
libclient "github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/client/mfa"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/modules"
"github.com/gravitational/teleport/lib/multiplexer"
Expand Down Expand Up @@ -1315,18 +1316,29 @@ func TestALPNSNIProxyAppAccess(t *testing.T) {
})

t.Run("teleterm app gateways cert renewal", func(t *testing.T) {
user, _ := pack.CreateUser(t)
tc := pack.MakeTeleportClient(t, user.GetName())

// test without per session MFA.
testTeletermAppGateway(t, pack, tc)
t.Run("without per-session MFA", func(t *testing.T) {
makeTC := func(t *testing.T) (*libclient.TeleportClient, mfa.WebauthnLoginFunc) {
user, _ := pack.CreateUser(t)
tc := pack.MakeTeleportClient(t, user.GetName())
return tc, nil
}
testTeletermAppGateway(t, pack, makeTC)
testTeletermAppGatewayTargetPortValidation(t, pack, makeTC)
})

t.Run("per session MFA", func(t *testing.T) {
// They update user's authentication to Webauthn so they must run after tests which do not use MFA.
t.Run("per-session MFA", func(t *testing.T) {
// They update clusters authentication to Webauthn so they must run after tests which do not use MFA.
requireSessionMFAAuthPref(ctx, t, pack.RootAuthServer(), "127.0.0.1")
requireSessionMFAAuthPref(ctx, t, pack.LeafAuthServer(), "127.0.0.1")
tc.WebauthnLogin = setupUserMFA(ctx, t, pack.RootAuthServer(), user.GetName(), "127.0.0.1")
testTeletermAppGateway(t, pack, tc)
makeTCAndWebauthnLogin := func(t *testing.T) (*libclient.TeleportClient, mfa.WebauthnLoginFunc) {
// Create a separate user for each tests to enable parallel tests that use per-session MFA.
// See the comment for webauthnLogin in setupUserMFA for more details.
user, _ := pack.CreateUser(t)
tc := pack.MakeTeleportClient(t, user.GetName())
webauthnLogin := setupUserMFA(ctx, t, pack.RootAuthServer(), user.GetName(), "127.0.0.1")
return tc, webauthnLogin
}
testTeletermAppGateway(t, pack, makeTCAndWebauthnLogin)
})
})
}
Expand Down
Loading

0 comments on commit 1477004

Please sign in to comment.