diff --git a/integration/appaccess/appaccess_test.go b/integration/appaccess/appaccess_test.go index 0c10f9c026fbc..9939de0abc0bc 100644 --- a/integration/appaccess/appaccess_test.go +++ b/integration/appaccess/appaccess_test.go @@ -732,6 +732,7 @@ func TestTCP(t *testing.T) { Username: sessionUsername, ClusterName: test.tlsConfigParams.clusterName, AppPublicAddr: test.tlsConfigParams.publicAddr, + AppTargetPort: test.tlsConfigParams.targetPort, }) test.tlsConfigParams.sessionID = ws.GetName() diff --git a/integration/appaccess/fixtures.go b/integration/appaccess/fixtures.go index 65afd9f506340..ed722091cbc9e 100644 --- a/integration/appaccess/fixtures.go +++ b/integration/appaccess/fixtures.go @@ -24,11 +24,13 @@ import ( "net/http" "net/http/httptest" "net/http/httputil" + "strconv" "testing" "time" "github.com/google/uuid" "github.com/gorilla/websocket" + "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" @@ -91,6 +93,11 @@ func SetupWithOptions(t *testing.T, opts AppTestOptions) *Pack { rootTCPTwoWayPublicAddr: "tcp-twoway.example.com", rootTCPTwoWayMessage: uuidWithLabel("tcp-twoway"), + rootTCPMultiPortAppName: "tcp-multiport-01", + rootTCPMultiPortPublicAddr: "tcp-multiport-01.example.com", + rootTCPMultiPortMessageAlpha: uuidWithLabel("tcp-multiport-01-alpha"), + rootTCPMultiPortMessageBeta: uuidWithLabel("tcp-multiport-01-beta"), + leafAppName: "app-02", leafAppPublicAddr: "app-02.example.com", leafAppClusterName: "leaf.example.com", @@ -108,6 +115,11 @@ func SetupWithOptions(t *testing.T, opts AppTestOptions) *Pack { leafTCPPublicAddr: "tcp-02.example.com", leafTCPMessage: uuidWithLabel("tcp-02"), + leafTCPMultiPortAppName: "tcp-multiport-02", + leafTCPMultiPortPublicAddr: "tcp-multiport-02.example.com", + leafTCPMultiPortMessageAlpha: uuidWithLabel("tcp-multiport-02-alpha"), + leafTCPMultiPortMessageBeta: uuidWithLabel("tcp-multiport-02-beta"), + jwtAppName: "app-03", jwtAppPublicAddr: "app-03.example.com", jwtAppClusterName: "example.com", @@ -174,6 +186,19 @@ func SetupWithOptions(t *testing.T, opts AppTestOptions) *Pack { c.Close() }) t.Cleanup(func() { rootTCPTwoWayServer.Close() }) + + // Two TCP servers for the multi-port TCP application in the root cluster. + rootTCPMultiPortServerAlpha := newTCPServer(t, func(c net.Conn) { + c.Write([]byte(p.rootTCPMultiPortMessageAlpha)) + c.Close() + }) + t.Cleanup(func() { rootTCPMultiPortServerAlpha.Close() }) + rootTCPMultiPortServerBeta := newTCPServer(t, func(c net.Conn) { + c.Write([]byte(p.rootTCPMultiPortMessageBeta)) + c.Close() + }) + t.Cleanup(func() { rootTCPMultiPortServerBeta.Close() }) + // HTTP server in leaf cluster. leafServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, p.leafMessage) @@ -197,6 +222,19 @@ func SetupWithOptions(t *testing.T, opts AppTestOptions) *Pack { c.Close() }) t.Cleanup(func() { leafTCPServer.Close() }) + + // Two TCP servers for the multi-port TCP application in the leaf cluster. + leafTCPMultiPortServerAlpha := newTCPServer(t, func(c net.Conn) { + c.Write([]byte(p.leafTCPMultiPortMessageAlpha)) + c.Close() + }) + t.Cleanup(func() { leafTCPMultiPortServerAlpha.Close() }) + leafTCPMultiPortServerBeta := newTCPServer(t, func(c net.Conn) { + c.Write([]byte(p.leafTCPMultiPortMessageBeta)) + c.Close() + }) + t.Cleanup(func() { leafTCPMultiPortServerBeta.Close() }) + // JWT server writes generated JWT token in the response. jwtServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, r.Header.Get(teleport.AppJWTHeader)) @@ -243,15 +281,31 @@ func SetupWithOptions(t *testing.T, opts AppTestOptions) *Pack { })) t.Cleanup(flushServer.Close) + rootMultiPortHost, rootTCPMultiPortAppPortAlpha, err := splitHostPort(rootTCPMultiPortServerAlpha.Addr().String()) + require.NoError(t, err) + _, rootTCPMultiPortAppPortBeta, err := splitHostPort(rootTCPMultiPortServerBeta.Addr().String()) + require.NoError(t, err) + + leafMultiPortHost, leafTCPMultiPortAppPortAlpha, err := splitHostPort(leafTCPMultiPortServerAlpha.Addr().String()) + require.NoError(t, err) + _, leafTCPMultiPortAppPortBeta, err := splitHostPort(leafTCPMultiPortServerBeta.Addr().String()) + require.NoError(t, err) + p.rootAppURI = rootServer.URL p.rootWSAppURI = rootWSServer.URL p.rootWSSAppURI = rootWSSServer.URL p.rootTCPAppURI = fmt.Sprintf("tcp://%v", rootTCPServer.Addr().String()) p.rootTCPTwoWayAppURI = fmt.Sprintf("tcp://%v", rootTCPTwoWayServer.Addr().String()) + p.rootTCPMultiPortAppURI = fmt.Sprintf("tcp://%v", rootMultiPortHost) + p.rootTCPMultiPortAppPortAlpha = rootTCPMultiPortAppPortAlpha + p.rootTCPMultiPortAppPortBeta = rootTCPMultiPortAppPortBeta p.leafAppURI = leafServer.URL p.leafWSAppURI = leafWSServer.URL p.leafWSSAppURI = leafWSSServer.URL p.leafTCPAppURI = fmt.Sprintf("tcp://%v", leafTCPServer.Addr().String()) + p.leafTCPMultiPortAppURI = fmt.Sprintf("tcp://%v", leafMultiPortHost) + p.leafTCPMultiPortAppPortAlpha = leafTCPMultiPortAppPortAlpha + p.leafTCPMultiPortAppPortBeta = leafTCPMultiPortAppPortBeta p.jwtAppURI = jwtServer.URL p.headerAppURI = headerServer.URL p.wsHeaderAppURI = wsHeaderServer.URL @@ -410,3 +464,17 @@ func newTCPServer(t *testing.T, handleConn func(net.Conn)) net.Listener { func uuidWithLabel(label string) string { return fmt.Sprintf("%s-%s", label, uuid.New().String()) } + +func splitHostPort(hostport string) (string, uint16, error) { + host, portString, err := net.SplitHostPort(hostport) + if err != nil { + return "", 0, trace.Wrap(err) + } + + port, err := strconv.ParseUint(portString, 10, 16) + if err != nil { + return "", 0, trace.Wrap(err) + } + + return host, uint16(port), nil +} diff --git a/integration/appaccess/pack.go b/integration/appaccess/pack.go index 2b619d19adf3d..ed335138ce227 100644 --- a/integration/appaccess/pack.go +++ b/integration/appaccess/pack.go @@ -106,6 +106,14 @@ type Pack struct { rootTCPTwoWayMessage string rootTCPTwoWayAppURI string + rootTCPMultiPortAppName string + rootTCPMultiPortPublicAddr string + rootTCPMultiPortMessageAlpha string + rootTCPMultiPortMessageBeta string + rootTCPMultiPortAppURI string + rootTCPMultiPortAppPortAlpha uint16 + rootTCPMultiPortAppPortBeta uint16 + jwtAppName string jwtAppPublicAddr string jwtAppClusterName string @@ -137,6 +145,14 @@ type Pack struct { leafTCPMessage string leafTCPAppURI string + leafTCPMultiPortAppName string + leafTCPMultiPortPublicAddr string + leafTCPMultiPortMessageAlpha string + leafTCPMultiPortMessageBeta string + leafTCPMultiPortAppURI string + leafTCPMultiPortAppPortAlpha uint16 + leafTCPMultiPortAppPortBeta uint16 + headerAppName string headerAppPublicAddr string headerAppClusterName string @@ -368,6 +384,7 @@ type CreateAppSessionParams struct { Username string ClusterName string AppPublicAddr string + AppTargetPort uint16 } func (p *Pack) CreateAppSession(t *testing.T, params CreateAppSessionParams) types.WebSession { @@ -383,8 +400,9 @@ func (p *Pack) CreateAppSession(t *testing.T, params CreateAppSessionParams) typ Traits: accessInfo.Traits, SessionTTL: time.Hour, }, - PublicAddr: params.AppPublicAddr, - ClusterName: params.ClusterName, + PublicAddr: params.AppPublicAddr, + ClusterName: params.ClusterName, + AppTargetPort: params.AppTargetPort, }) require.NoError(t, err) @@ -492,6 +510,7 @@ type tlsConfigParams struct { publicAddr string clusterName string pinnedIP string + targetPort uint16 } // makeTLSConfig returns TLS config suitable for making an app access request. @@ -516,6 +535,7 @@ func (p *Pack) makeTLSConfig(t *testing.T, params tlsConfigParams) *tls.Config { Username: params.username, TTL: time.Hour, PublicAddr: params.publicAddr, + TargetPort: params.targetPort, ClusterName: params.clusterName, SessionID: params.sessionID, PinnedIP: params.pinnedIP, @@ -732,6 +752,19 @@ func (p *Pack) startRootAppServers(t *testing.T, count int, opts AppTestOptions) URI: p.rootTCPTwoWayAppURI, PublicAddr: p.rootTCPTwoWayPublicAddr, }, + { + Name: p.rootTCPMultiPortAppName, + URI: p.rootTCPMultiPortAppURI, + PublicAddr: p.rootTCPMultiPortPublicAddr, + TCPPorts: []servicecfg.PortRange{ + servicecfg.PortRange{ + Port: p.rootTCPMultiPortAppPortAlpha, + }, + servicecfg.PortRange{ + Port: p.rootTCPMultiPortAppPortBeta, + }, + }, + }, { Name: p.jwtAppName, URI: p.jwtAppURI, @@ -885,6 +918,19 @@ func (p *Pack) startLeafAppServers(t *testing.T, count int, opts AppTestOptions) URI: p.leafTCPAppURI, PublicAddr: p.leafTCPPublicAddr, }, + { + Name: p.leafTCPMultiPortAppName, + URI: p.leafTCPMultiPortAppURI, + PublicAddr: p.leafTCPMultiPortPublicAddr, + TCPPorts: []servicecfg.PortRange{ + servicecfg.PortRange{ + Port: p.leafTCPMultiPortAppPortAlpha, + }, + servicecfg.PortRange{ + Port: p.leafTCPMultiPortAppPortBeta, + }, + }, + }, { Name: "dumper-leaf", URI: p.dumperAppURI, diff --git a/lib/auth/auth.go b/lib/auth/auth.go index fe0b6798f7665..9e9c644dfdc6d 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2413,6 +2413,8 @@ type AppTestCertRequest struct { TTL time.Duration // PublicAddr is the application public address. Used for routing. PublicAddr string + // TargetPort is the port to which connections to multi-port TCP apps should be routed to. + TargetPort uint16 // ClusterName is the name of the cluster application resides in. Used for routing. ClusterName string // SessionID is the optional session ID to encode. Used for routing. @@ -2472,6 +2474,7 @@ func (a *Server) GenerateUserAppTestCert(req AppTestCertRequest) ([]byte, error) // Add in the application routing information. appSessionID: sessionID, appPublicAddr: req.PublicAddr, + appTargetPort: req.TargetPort, appClusterName: req.ClusterName, awsRoleARN: req.AWSRoleARN, azureIdentity: req.AzureIdentity,