diff --git a/lib/srv/alpnproxy/dialer.go b/api/client/alpn.go
similarity index 85%
rename from lib/srv/alpnproxy/dialer.go
rename to api/client/alpn.go
index f4b3e183bb97a..15ac2451194cc 100644
--- a/lib/srv/alpnproxy/dialer.go
+++ b/api/client/alpn.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package alpnproxy
+package client
import (
"context"
@@ -23,16 +23,8 @@ import (
"time"
"github.com/gravitational/trace"
-
- apiclient "github.com/gravitational/teleport/api/client"
)
-// ContextDialer represents network dialer interface that uses context
-type ContextDialer interface {
- // DialContext is a function that dials the specified address
- DialContext(ctx context.Context, network, addr string) (net.Conn, error)
-}
-
// ALPNDialerConfig is the config for ALPNDialer.
type ALPNDialerConfig struct {
// KeepAlivePeriod defines period between keep alives.
@@ -66,7 +58,7 @@ func (d ALPNDialer) DialContext(ctx context.Context, network, addr string) (net.
return nil, trace.BadParameter("missing TLS config")
}
- dialer := apiclient.NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, apiclient.WithTLSConfig(d.cfg.TLSConfig))
+ dialer := NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, WithTLSConfig(d.cfg.TLSConfig))
if d.cfg.ALPNConnUpgradeRequired {
dialer = newALPNConnUpgradeDialer(dialer, &tls.Config{
InsecureSkipVerify: d.cfg.TLSConfig.InsecureSkipVerify,
diff --git a/lib/srv/alpnproxy/conn_upgrade.go b/api/client/alpn_conn_upgrade.go
similarity index 92%
rename from lib/srv/alpnproxy/conn_upgrade.go
rename to api/client/alpn_conn_upgrade.go
index 1425ae70ee274..c82f91dd7ff63 100644
--- a/lib/srv/alpnproxy/conn_upgrade.go
+++ b/api/client/alpn_conn_upgrade.go
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package alpnproxy
+package client
import (
"bufio"
@@ -30,11 +30,9 @@ import (
"github.com/gravitational/trace"
"github.com/sirupsen/logrus"
- "github.com/gravitational/teleport"
- apiclient "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/api/defaults"
"github.com/gravitational/teleport/api/utils"
- "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
)
// IsALPNConnUpgradeRequired returns true if a tunnel is required through a HTTP
@@ -57,7 +55,7 @@ func IsALPNConnUpgradeRequired(addr string, insecure bool) bool {
Timeout: defaults.DefaultIOTimeout,
}
tlsConfig := &tls.Config{
- NextProtos: []string{string(common.ProtocolReverseTunnel)},
+ NextProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)},
InsecureSkipVerify: insecure,
}
testConn, err := tls.DialWithDialer(netDialer, "tcp", addr, tlsConfig)
@@ -145,12 +143,12 @@ func isALPNConnUpgradeRequiredByEnv(addr, envValue string) bool {
// alpnConnUpgradeDialer makes an "HTTP" upgrade call to the Proxy Service then
// tunnels the connection with this connection upgrade.
type alpnConnUpgradeDialer struct {
- dialer apiclient.ContextDialer
+ dialer ContextDialer
tlsConfig *tls.Config
}
// newALPNConnUpgradeDialer creates a new alpnConnUpgradeDialer.
-func newALPNConnUpgradeDialer(dialer apiclient.ContextDialer, tlsConfig *tls.Config) ContextDialer {
+func newALPNConnUpgradeDialer(dialer ContextDialer, tlsConfig *tls.Config) ContextDialer {
return &alpnConnUpgradeDialer{
dialer: dialer,
tlsConfig: tlsConfig,
@@ -187,7 +185,7 @@ func (d alpnConnUpgradeDialer) DialContext(ctx context.Context, network, addr st
err = upgradeConnThroughWebAPI(tlsConn, url.URL{
Host: addr,
Scheme: "https",
- Path: teleport.WebAPIConnUpgrade,
+ Path: constants.WebAPIConnUpgrade,
})
if err != nil {
defer tlsConn.Close()
@@ -203,7 +201,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error {
}
// For now, only "alpn" is supported.
- req.Header.Add(teleport.WebAPIConnUpgradeHeader, teleport.WebAPIConnUpgradeTypeALPN)
+ req.Header.Add(constants.WebAPIConnUpgradeHeader, constants.WebAPIConnUpgradeTypeALPN)
// Send the request and check if upgrade is successful.
if err = req.Write(conn); err != nil {
@@ -219,7 +217,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error {
if http.StatusNotFound == resp.StatusCode {
return trace.NotImplemented(
"connection upgrade call to %q failed with status code %v. Please upgrade the server and try again.",
- teleport.WebAPIConnUpgrade,
+ constants.WebAPIConnUpgrade,
resp.StatusCode,
)
}
diff --git a/lib/srv/alpnproxy/conn_upgrade_test.go b/api/client/alpn_conn_upgrade_test.go
similarity index 85%
rename from lib/srv/alpnproxy/conn_upgrade_test.go
rename to api/client/alpn_conn_upgrade_test.go
index c01d01eb4d131..4c02b3ebfebb6 100644
--- a/lib/srv/alpnproxy/conn_upgrade_test.go
+++ b/api/client/alpn_conn_upgrade_test.go
@@ -14,13 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-package alpnproxy
+package client
import (
"context"
"crypto/tls"
"crypto/x509"
- "crypto/x509/pkix"
"errors"
"net"
"net/http"
@@ -31,11 +30,8 @@ import (
"github.com/stretchr/testify/require"
- "github.com/gravitational/teleport"
- apiclient "github.com/gravitational/teleport/api/client"
- "github.com/gravitational/teleport/lib/defaults"
- "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
- "github.com/gravitational/teleport/lib/tlsca"
+ "github.com/gravitational/teleport/api/constants"
+ "github.com/gravitational/teleport/api/fixtures"
)
func TestIsALPNConnUpgradeRequired(t *testing.T) {
@@ -55,7 +51,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) {
},
{
name: "upgrade not required (proto negotiated)",
- serverProtos: []string{string(common.ProtocolReverseTunnel)},
+ serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)},
insecure: true,
expectedResult: false,
},
@@ -67,7 +63,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) {
},
{
name: "upgrade not required (other handshake error)",
- serverProtos: []string{string(common.ProtocolReverseTunnel)},
+ serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)},
insecure: false, // to cause handshake error
expectedResult: false,
},
@@ -138,7 +134,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) {
pool.AddCert(server.Certificate())
tlsConfig := &tls.Config{RootCAs: pool}
- preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig))
+ preDialer := NewDialer(ctx, 0, 5*time.Second)
dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig)
conn, err := dialer.DialContext(ctx, "tcp", addr.Host)
require.NoError(t, err)
@@ -158,7 +154,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) {
require.NoError(t, err)
tlsConfig := &tls.Config{InsecureSkipVerify: true}
- preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig))
+ preDialer := NewDialer(ctx, 0, 5*time.Second)
dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig)
_, err = dialer.DialContext(ctx, "tcp", addr.Host)
require.Error(t, err)
@@ -207,12 +203,7 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe
listener.Close()
})
- caKey, caCert, err := tlsca.GenerateSelfSignedCA(pkix.Name{
- CommonName: "localhost",
- }, []string{"localhost"}, defaults.CATTL)
- require.NoError(t, err)
-
- cert, err := tls.X509KeyPair(caCert, caKey)
+ cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM))
require.NoError(t, err)
m := &mockALPNServer{
@@ -228,8 +219,8 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe
// upgrade request and sends back some data inside the tunnel.
func mockConnUpgradeHandler(t *testing.T, upgradeType string, write []byte) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- require.Equal(t, teleport.WebAPIConnUpgrade, r.URL.Path)
- require.Equal(t, upgradeType, r.Header.Get(teleport.WebAPIConnUpgradeHeader))
+ require.Equal(t, constants.WebAPIConnUpgrade, r.URL.Path)
+ require.Equal(t, upgradeType, r.Header.Get(constants.WebAPIConnUpgradeHeader))
hj, ok := w.(http.Hijacker)
require.True(t, ok)
diff --git a/api/constants/constants.go b/api/constants/constants.go
index 50c409cd6aca1..47b3b6c513c29 100644
--- a/api/constants/constants.go
+++ b/api/constants/constants.go
@@ -402,3 +402,15 @@ const (
// TimeoutGetClusterAlerts is the timeout for grabbing cluster alerts from tctl and tsh
TimeoutGetClusterAlerts = time.Millisecond * 500
)
+
+const (
+ // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade
+ // call.
+ WebAPIConnUpgrade = "/webapi/connectionupgrade"
+ // WebAPIConnUpgradeHeader is the header used to indicate the requested
+ // connection upgrade types in the connection upgrade API.
+ WebAPIConnUpgradeHeader = "Upgrade"
+ // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies
+ // the upgraded connection should be handled by the ALPN handler.
+ WebAPIConnUpgradeTypeALPN = "alpn"
+)
diff --git a/api/fixtures/fixtures.go b/api/fixtures/fixtures.go
new file mode 100644
index 0000000000000..573e4d3c9f651
--- /dev/null
+++ b/api/fixtures/fixtures.go
@@ -0,0 +1,64 @@
+// Copyright 2023 Gravitational, Inc
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fixtures
+
+const (
+ TLSCACertPEM = `-----BEGIN CERTIFICATE-----
+MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA
+MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs
+aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa
+MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j
+YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI
+gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi
+fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4
+PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp
+1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB
+sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD
+VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ
+o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1
+IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m
+NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx
+QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu
+vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ==
+-----END CERTIFICATE-----`
+ TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ
+m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6
+m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq
+qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG
+saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu
+F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9
+k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD
+ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ
+fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ
+jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE
+7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz
+Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e
+WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA
+d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb
+hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU
+ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1
+y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI
+ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs
+F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE
+FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY
+rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z
+jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh
+k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA
+LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8
++ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA
+-----END RSA PRIVATE KEY-----`
+)
diff --git a/api/utils/pingconn/pingconn.go b/api/utils/pingconn/pingconn.go
new file mode 100644
index 0000000000000..1578a1110b90c
--- /dev/null
+++ b/api/utils/pingconn/pingconn.go
@@ -0,0 +1,132 @@
+/*
+Copyright 2023 Gravitational, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package pingconn
+
+import (
+ "crypto/tls"
+ "encoding/binary"
+ "math"
+ "sync"
+
+ "github.com/gravitational/trace"
+)
+
+// New returns a ping connection wrapping the provided net.Conn.
+func New(conn *tls.Conn) *PingConn {
+ return &PingConn{Conn: conn}
+}
+
+// PingConn wraps a *tls.Conn and add ping capabilities to it, including the
+// `WritePing` function and `Read` (which excludes ping packets).
+//
+// When using this connection, the packets written will contain an initial data:
+// the packet size. When reading, this information is taken into account, but it
+// is not returned to the caller.
+//
+// Ping messages have a packet size of zero and are produced only when
+// `WritePing` is called. On `Read`, any Ping packet is discarded.
+type PingConn struct {
+ //net.Conn
+ *tls.Conn
+
+ muRead sync.Mutex
+ muWrite sync.Mutex
+
+ // currentSize size of bytes of the current packet.
+ currentSize uint32
+}
+
+// Read reads content from the underlaying connection, discarding any ping
+// messages it finds.
+func (c *PingConn) Read(p []byte) (int, error) {
+ c.muRead.Lock()
+ defer c.muRead.Unlock()
+
+ err := c.discardPingReads()
+ if err != nil {
+ return 0, err
+ }
+
+ // Check if the current size is larger than the provided buffer.
+ readSize := c.currentSize
+ if c.currentSize > uint32(len(p)) {
+ readSize = uint32(len(p))
+ }
+
+ n, err := c.Conn.Read(p[:readSize])
+ c.currentSize -= uint32(n)
+
+ return n, err
+}
+
+// WritePing writes the ping packet to the connection.
+func (c *PingConn) WritePing() error {
+ c.muWrite.Lock()
+ defer c.muWrite.Unlock()
+
+ return binary.Write(c.Conn, binary.BigEndian, uint32(0))
+}
+
+// discardPingReads reads from the wrapped net.Conn until it encounters a
+// non-ping packet.
+func (c *PingConn) discardPingReads() error {
+ for c.currentSize == 0 {
+ err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// Write writes provided content to the underlying connection with proper
+// protocol fields.
+func (c *PingConn) Write(p []byte) (int, error) {
+ c.muWrite.Lock()
+ defer c.muWrite.Unlock()
+
+ // Avoid overflow when casting data length. It is only present to avoid
+ // panicking if the size cannot be cast. Callers should handle packet length
+ // limits, such as protocol implementations and audits.
+ if uint64(len(p)) > math.MaxUint32 {
+ return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32))
+ }
+
+ size := uint32(len(p))
+ if size == 0 {
+ return 0, nil
+ }
+
+ // Write packet size.
+ if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil {
+ return 0, trace.Wrap(err)
+ }
+
+ // Iterate until everything is written.
+ var written int
+ for written < len(p) {
+ n, err := c.Conn.Write(p)
+ written += n
+
+ if err != nil {
+ return written, trace.Wrap(err)
+ }
+ }
+
+ return written, nil
+}
diff --git a/lib/srv/alpnproxy/conn_test.go b/api/utils/pingconn/pingconn_test.go
similarity index 96%
rename from lib/srv/alpnproxy/conn_test.go
rename to api/utils/pingconn/pingconn_test.go
index d91175d628a22..69a6a32cc5c47 100644
--- a/lib/srv/alpnproxy/conn_test.go
+++ b/api/utils/pingconn/pingconn_test.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package alpnproxy
+package pingconn
import (
"bytes"
@@ -26,6 +26,8 @@ import (
"time"
"github.com/stretchr/testify/require"
+
+ "github.com/gravitational/teleport/api/fixtures"
)
func TestPingConnection(t *testing.T) {
@@ -273,7 +275,7 @@ func makePingConn(t *testing.T) (*PingConn, *PingConn) {
writer, reader := net.Pipe()
tlsWriter, tlsReader := makeTLSConn(t, writer, reader)
- return NewPingConn(tlsWriter), NewPingConn(tlsReader)
+ return New(tlsWriter), New(tlsReader)
}
// makeBufferedPingConn creates connections to have asynchronous writes.
@@ -321,7 +323,7 @@ func makeBufferedPingConn(t *testing.T) (*PingConn, *PingConn) {
}
tlsConnA, tlsConnB := makeTLSConn(t, connSlice[0], connSlice[1])
- return NewPingConn(tlsConnA), NewPingConn(tlsConnB)
+ return New(tlsConnA), New(tlsConnB)
}
// makeTLSConn take two connections (client and server) and wrap them into TLS
@@ -334,10 +336,13 @@ func makeTLSConn(t *testing.T, server, client net.Conn) (*tls.Conn, *tls.Conn) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
+ cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM))
+ require.NoError(t, err)
+
// Server
go func() {
tlsConn := tls.Server(server, &tls.Config{
- Certificates: []tls.Certificate{mustGenCertSignedWithCA(t, mustGenSelfSignedCert(t))},
+ Certificates: []tls.Certificate{cert},
})
tlsConnChan <- struct {
*tls.Conn
diff --git a/constants.go b/constants.go
index 4e79e5636a0a5..e21f46bda3423 100644
--- a/constants.go
+++ b/constants.go
@@ -803,18 +803,6 @@ const UserSingleUseCertTTL = time.Minute
// cf. RFC 7230 ยง 2.7.2.
const StandardHTTPSPort = 443
-const (
- // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade
- // call.
- WebAPIConnUpgrade = "/webapi/connectionupgrade"
- // WebAPIConnUpgradeHeader is the header used to indicate the requested
- // connection upgrade types in the connection upgrade API.
- WebAPIConnUpgradeHeader = "Upgrade"
- // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies
- // the upgraded connection should be handled by the ALPN handler.
- WebAPIConnUpgradeTypeALPN = "alpn"
-)
-
const (
// KubeSessionDisplayParticipantRequirementsQueryParam is the query parameter used to
// indicate that the client wants to display the participant requirements
diff --git a/lib/client/api.go b/lib/client/api.go
index c93d0809e5ac0..43112e98d1e00 100644
--- a/lib/client/api.go
+++ b/lib/client/api.go
@@ -75,7 +75,6 @@ import (
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/shell"
- "github.com/gravitational/teleport/lib/srv/alpnproxy"
alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common"
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/sshutils/scp"
@@ -623,7 +622,7 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error {
log.Warnf("Unable to parse dynamic port forwarding in user profile: %v.", err)
}
- if required, ok := alpnproxy.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok {
+ if required, ok := client.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok {
c.TLSRoutingConnUpgradeRequired = required
}
log.Infof("ALPN connection upgrade required for %q: %v.", c.WebProxyAddr, c.TLSRoutingConnUpgradeRequired)
@@ -3095,7 +3094,7 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) {
}
// Perform the ALPN test once at login.
- tc.TLSRoutingConnUpgradeRequired = alpnproxy.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify)
+ tc.TLSRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify)
// Get the SSHLoginFunc that matches client and cluster settings.
sshLoginFunc, err := tc.getSSHLoginFunc(pr)
diff --git a/lib/fixtures/fixtures.go b/lib/fixtures/fixtures.go
index 9d6ce39dc1a4b..cf86dd07d1959 100644
--- a/lib/fixtures/fixtures.go
+++ b/lib/fixtures/fixtures.go
@@ -19,6 +19,8 @@ import (
"testing"
"github.com/gravitational/trace"
+
+ apifixtures "github.com/gravitational/teleport/api/fixtures"
)
// AssertNotFound expects not found error
@@ -149,52 +151,8 @@ spec:
pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwRurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified`
const (
- TLSCACertPEM = `-----BEGIN CERTIFICATE-----
-MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA
-MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs
-aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa
-MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j
-YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
-AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI
-gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi
-fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4
-PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp
-1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB
-sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD
-VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ
-o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1
-IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m
-NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx
-QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu
-vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ==
------END CERTIFICATE-----`
- TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
-MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ
-m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6
-m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq
-qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG
-saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu
-F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9
-k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD
-ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ
-fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ
-jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE
-7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz
-Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e
-WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA
-d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb
-hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU
-ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1
-y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI
-ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs
-F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE
-FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY
-rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z
-jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh
-k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA
-LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8
-+ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA
------END RSA PRIVATE KEY-----`
+ TLSCACertPEM = apifixtures.TLSCACertPEM
+ TLSCAKeyPEM = apifixtures.TLSCAKeyPEM
// Backwards-compatibility alias for teleport.e
SigningCertPEM = TLSCACertPEM
)
diff --git a/lib/srv/alpnproxy/conn.go b/lib/srv/alpnproxy/conn.go
index ef566dbb9fd81..c8bed17e08112 100644
--- a/lib/srv/alpnproxy/conn.go
+++ b/lib/srv/alpnproxy/conn.go
@@ -17,16 +17,10 @@ limitations under the License.
package alpnproxy
import (
- "crypto/tls"
- "encoding/binary"
"io"
- "math"
"net"
- "sync"
"time"
- "github.com/gravitational/trace"
-
"github.com/gravitational/teleport/lib/utils"
)
@@ -103,109 +97,3 @@ func (conn readOnlyConn) RemoteAddr() net.Addr { return &utils.Net
func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil }
func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil }
func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil }
-
-// NewPingConn returns a ping connection wrapping the provided net.Conn.
-func NewPingConn(conn *tls.Conn) *PingConn {
- return &PingConn{Conn: conn}
-}
-
-// PingConn wraps a *tls.Conn and add ping capabilities to it, including the
-// `WritePing` function and `Read` (which excludes ping packets).
-//
-// When using this connection, the packets written will contain an initial data:
-// the packet size. When reading, this information is taken into account, but it
-// is not returned to the caller.
-//
-// Ping messages have a packet size of zero and are produced only when
-// `WritePing` is called. On `Read`, any Ping packet is discarded.
-type PingConn struct {
- //net.Conn
- *tls.Conn
-
- muRead sync.Mutex
- muWrite sync.Mutex
-
- // currentSize size of bytes of the current packet.
- currentSize uint32
-}
-
-// Read reads content from the underlaying connection, discarding any ping
-// messages it finds.
-func (c *PingConn) Read(p []byte) (int, error) {
- c.muRead.Lock()
- defer c.muRead.Unlock()
-
- err := c.discardPingReads()
- if err != nil {
- return 0, err
- }
-
- // Check if the current size is larger than the provided buffer.
- readSize := c.currentSize
- if c.currentSize > uint32(len(p)) {
- readSize = uint32(len(p))
- }
-
- n, err := c.Conn.Read(p[:readSize])
- c.currentSize -= uint32(n)
-
- return n, err
-}
-
-// WritePing writes the ping packet to the connection.
-func (c *PingConn) WritePing() error {
- c.muWrite.Lock()
- defer c.muWrite.Unlock()
-
- return binary.Write(c.Conn, binary.BigEndian, uint32(0))
-}
-
-// discardPingReads reads from the wrapped net.Conn until it encounters a
-// non-ping packet.
-func (c *PingConn) discardPingReads() error {
- for c.currentSize == 0 {
- err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// Write writes provided content to the underlying connection with proper
-// protocol fields.
-func (c *PingConn) Write(p []byte) (int, error) {
- c.muWrite.Lock()
- defer c.muWrite.Unlock()
-
- // Avoid overflow when casting data length. It is only present to avoid
- // panicking if the size cannot be cast. Callers should handle packet length
- // limits, such as protocol implementations and audits.
- if uint64(len(p)) > math.MaxUint32 {
- return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32))
- }
-
- size := uint32(len(p))
- if size == 0 {
- return 0, nil
- }
-
- // Write packet size.
- if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil {
- return 0, trace.Wrap(err)
- }
-
- // Iterate until everything is written.
- var written int
- for written < len(p) {
- n, err := c.Conn.Write(p)
- written += n
-
- if err != nil {
- return written, trace.Wrap(err)
- }
- }
-
- return written, nil
-}
diff --git a/lib/srv/alpnproxy/local_proxy.go b/lib/srv/alpnproxy/local_proxy.go
index 103a45af5e60d..d914002faf7d9 100644
--- a/lib/srv/alpnproxy/local_proxy.go
+++ b/lib/srv/alpnproxy/local_proxy.go
@@ -34,6 +34,8 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
+ "github.com/gravitational/teleport/api/client"
+ "github.com/gravitational/teleport/api/utils/pingconn"
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
commonApp "github.com/gravitational/teleport/lib/srv/app/common"
"github.com/gravitational/teleport/lib/tlsca"
@@ -218,7 +220,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC
return trace.Wrap(err)
}
- tlsConn, err := DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs))
+ tlsConn, err := client.DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs))
if err != nil {
return trace.Wrap(err)
}
@@ -227,7 +229,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC
var upstreamConn net.Conn = tlsConn
if common.IsPingProtocol(common.Protocol(tlsConn.ConnectionState().NegotiatedProtocol)) {
l.cfg.Log.Debug("Using ping connection")
- upstreamConn = NewPingConn(tlsConn)
+ upstreamConn = pingconn.New(tlsConn)
}
return trace.Wrap(utils.ProxyConn(ctx, downstreamConn, upstreamConn))
@@ -243,8 +245,8 @@ func (l *LocalProxy) Close() error {
return nil
}
-func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) ALPNDialerConfig {
- return ALPNDialerConfig{
+func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) client.ALPNDialerConfig {
+ return client.ALPNDialerConfig{
ALPNConnUpgradeRequired: l.cfg.ALPNConnUpgradeRequired,
TLSConfig: &tls.Config{
NextProtos: common.ProtocolsToString(l.cfg.Protocols),
@@ -285,7 +287,7 @@ func (l *LocalProxy) makeHTTPReverseProxy(certs []tls.Certificate) *httputil.Rev
http.Error(w, http.StatusText(code), code)
},
Transport: &http.Transport{
- DialTLSContext: NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext,
+ DialTLSContext: client.NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext,
},
}
}
diff --git a/lib/srv/alpnproxy/local_proxy_config_opt.go b/lib/srv/alpnproxy/local_proxy_config_opt.go
index 20b5e61e23dde..f2af0b5ed81ae 100644
--- a/lib/srv/alpnproxy/local_proxy_config_opt.go
+++ b/lib/srv/alpnproxy/local_proxy_config_opt.go
@@ -24,6 +24,7 @@ import (
"github.com/gravitational/trace"
+ "github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
)
@@ -41,7 +42,7 @@ type GetClusterCACertPoolFunc func(ctx context.Context) (*x509.CertPool, error)
// already been set.
func WithALPNConnUpgradeTest(ctx context.Context, getClusterCertPool GetClusterCACertPoolFunc) LocalProxyConfigOpt {
return func(config *LocalProxyConfig) error {
- config.ALPNConnUpgradeRequired = IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify)
+ config.ALPNConnUpgradeRequired = client.IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify)
return trace.Wrap(WithClusterCAsIfConnUpgrade(ctx, getClusterCertPool)(config))
}
}
diff --git a/lib/srv/alpnproxy/proxy.go b/lib/srv/alpnproxy/proxy.go
index 4ac5613fb538d..2c0595cf7f8cf 100644
--- a/lib/srv/alpnproxy/proxy.go
+++ b/lib/srv/alpnproxy/proxy.go
@@ -33,6 +33,7 @@ import (
"github.com/sirupsen/logrus"
"github.com/gravitational/teleport/api/constants"
+ "github.com/gravitational/teleport/api/utils/pingconn"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
@@ -410,7 +411,7 @@ func (p *Proxy) handleConn(ctx context.Context, clientConn net.Conn, defaultOver
// handlePingConnection starts the server ping routine and returns `pingConn`.
func (p *Proxy) handlePingConnection(ctx context.Context, conn *tls.Conn) net.Conn {
- pingConn := NewPingConn(conn)
+ pingConn := pingconn.New(conn)
// Start ping routine. It will continuously send pings in a defined
// interval.
diff --git a/lib/srv/alpnproxy/proxy_test.go b/lib/srv/alpnproxy/proxy_test.go
index 6c81f62696de1..7e7e9451c8132 100644
--- a/lib/srv/alpnproxy/proxy_test.go
+++ b/lib/srv/alpnproxy/proxy_test.go
@@ -32,6 +32,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/gravitational/teleport/api/constants"
+ "github.com/gravitational/teleport/api/utils/pingconn"
"github.com/gravitational/teleport/lib/srv/alpnproxy/common"
"github.com/gravitational/teleport/lib/srv/db/dbutils"
"github.com/gravitational/teleport/lib/tlsca"
@@ -191,7 +192,7 @@ func TestProxyTLSDatabaseHandler(t *testing.T) {
})
require.NoError(t, err)
- conn := NewPingConn(baseConn)
+ conn := pingconn.New(baseConn)
tlsConn := tls.Client(conn, &tls.Config{
Certificates: []tls.Certificate{
clientCert,
diff --git a/lib/web/conn_upgrade.go b/lib/web/conn_upgrade.go
index 9f1638a74a2d2..cdfe3dae0556a 100644
--- a/lib/web/conn_upgrade.go
+++ b/lib/web/conn_upgrade.go
@@ -25,17 +25,17 @@ import (
"github.com/gravitational/trace"
"github.com/julienschmidt/httprouter"
- "github.com/gravitational/teleport"
+ "github.com/gravitational/teleport/api/constants"
"github.com/gravitational/teleport/lib/utils"
)
// selectConnectionUpgrade selects the requested upgrade type and returns the
// corresponding handler.
func (h *Handler) selectConnectionUpgrade(r *http.Request) (string, ConnectionHandler, error) {
- upgrades := r.Header.Values(teleport.WebAPIConnUpgradeHeader)
+ upgrades := r.Header.Values(constants.WebAPIConnUpgradeHeader)
for _, upgradeType := range upgrades {
switch upgradeType {
- case teleport.WebAPIConnUpgradeTypeALPN:
+ case constants.WebAPIConnUpgradeTypeALPN:
return upgradeType, h.upgradeALPN, nil
}
}
@@ -90,7 +90,7 @@ func (h *Handler) upgradeALPN(ctx context.Context, conn net.Conn) error {
func writeUpgradeResponse(w io.Writer, upgradeType string) error {
header := make(http.Header)
- header.Add(teleport.WebAPIConnUpgradeHeader, upgradeType)
+ header.Add(constants.WebAPIConnUpgradeHeader, upgradeType)
response := &http.Response{
Status: http.StatusText(http.StatusSwitchingProtocols),
StatusCode: http.StatusSwitchingProtocols,