mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-19 01:58:44 +08:00
Remove legacy ALPN connection upgrade support (#63339)
Remove server-side support for legacy (non-WebSocket) ALPN connection upgrades. Client-side support was removed in v18.0.0 (#52573).
This commit is contained in:
@@ -489,17 +489,11 @@ const (
|
||||
// WebAPIConnUpgradeHeader is the header used to indicate the requested
|
||||
// connection upgrade types in the connection upgrade API.
|
||||
WebAPIConnUpgradeHeader = "Upgrade"
|
||||
// WebAPIConnUpgradeTeleportHeader is a Teleport-specific header used to
|
||||
// indicate the requested connection upgrade types in the connection
|
||||
// upgrade API. This header is sent in addition to "Upgrade" header in case
|
||||
// a load balancer/reverse proxy removes "Upgrade".
|
||||
WebAPIConnUpgradeTeleportHeader = "X-Teleport-Upgrade"
|
||||
// WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies
|
||||
// the upgraded connection should be handled by the ALPN handler.
|
||||
// WebAPIConnUpgradeTypeALPN is a WebSocket subprotocol identifier for
|
||||
// ALPN connection upgrades.
|
||||
WebAPIConnUpgradeTypeALPN = "alpn"
|
||||
// WebAPIConnUpgradeTypeALPNPing is a connection upgrade type that
|
||||
// specifies the upgraded connection should be handled by the ALPN handler
|
||||
// wrapped with the Ping protocol.
|
||||
// WebAPIConnUpgradeTypeALPNPing is a WebSocket subprotocol identifier for
|
||||
// ALPN connection upgrades with WebSocket ping frames enabled.
|
||||
//
|
||||
// This should be used when the tunneled TLS Routing protocol cannot keep
|
||||
// long-lived connections alive as L7 LB usually ignores TCP keepalives and
|
||||
|
||||
+4
-83
@@ -36,68 +36,19 @@ import (
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/utils/pingconn"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
logutils "github.com/gravitational/teleport/lib/utils/log"
|
||||
)
|
||||
|
||||
// selectConnectionUpgrade selects the requested upgrade type and returns the
|
||||
// corresponding handler.
|
||||
func (h *Handler) selectConnectionUpgrade(r *http.Request) (string, ConnectionHandler, error) {
|
||||
upgrades := append(
|
||||
r.Header.Values(constants.WebAPIConnUpgradeTeleportHeader),
|
||||
r.Header.Values(constants.WebAPIConnUpgradeHeader)...,
|
||||
)
|
||||
|
||||
// Prefer WebSocket when multiple types are provided.
|
||||
switch {
|
||||
case slices.Contains(upgrades, constants.WebAPIConnUpgradeTypeWebSocket):
|
||||
return constants.WebAPIConnUpgradeTypeWebSocket, h.upgradeALPN, nil
|
||||
case slices.Contains(upgrades, constants.WebAPIConnUpgradeTypeALPNPing):
|
||||
return constants.WebAPIConnUpgradeTypeALPNPing, h.upgradeALPNWithPing, nil
|
||||
case slices.Contains(upgrades, constants.WebAPIConnUpgradeTypeALPN):
|
||||
return constants.WebAPIConnUpgradeTypeALPN, h.upgradeALPN, nil
|
||||
default:
|
||||
return "", nil, trace.NotFound("unsupported upgrade types: %v", upgrades)
|
||||
}
|
||||
}
|
||||
|
||||
// connectionUpgrade handles connection upgrades.
|
||||
func (h *Handler) connectionUpgrade(w http.ResponseWriter, r *http.Request, p httprouter.Params) (any, error) {
|
||||
upgradeType, upgradeHandler, err := h.selectConnectionUpgrade(r)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
upgrades := r.Header.Values(constants.WebAPIConnUpgradeHeader)
|
||||
if !slices.Contains(upgrades, constants.WebAPIConnUpgradeTypeWebSocket) {
|
||||
return nil, trace.NotFound("unsupported upgrade types: %v", upgrades)
|
||||
}
|
||||
|
||||
if upgradeType == constants.WebAPIConnUpgradeTypeWebSocket {
|
||||
return h.upgradeALPNWebSocket(w, r, upgradeHandler)
|
||||
}
|
||||
|
||||
// TODO(greedy52) DELETE legacy upgrade in 19.0. Client side is deprecated
|
||||
// in 18.0.
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, trace.BadParameter("failed to hijack connection")
|
||||
}
|
||||
|
||||
conn, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Since w is hijacked, there is no point returning an error for response
|
||||
// starting at this point.
|
||||
if err := writeUpgradeResponse(conn, upgradeType); err != nil {
|
||||
h.logger.ErrorContext(r.Context(), "Failed to write upgrade response.", "error", err)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := upgradeHandler(r.Context(), conn); err != nil && !utils.IsOKNetworkError(err) {
|
||||
h.logger.ErrorContext(r.Context(), "Failed to handle upgrade request.", "type", upgradeType, "error", err)
|
||||
}
|
||||
return nil, nil
|
||||
return h.upgradeALPNWebSocket(w, r, h.upgradeALPN)
|
||||
}
|
||||
|
||||
func (h *Handler) upgradeALPNWebSocket(w http.ResponseWriter, r *http.Request, upgradeHandler ConnectionHandler) (any, error) {
|
||||
@@ -165,21 +116,6 @@ func (h *Handler) upgradeALPN(ctx context.Context, conn net.Conn) error {
|
||||
return h.cfg.ALPNHandler(ctx, waitConn)
|
||||
}
|
||||
|
||||
func (h *Handler) upgradeALPNWithPing(ctx context.Context, conn net.Conn) error {
|
||||
if h.cfg.ALPNHandler == nil {
|
||||
return trace.BadParameter("missing ALPNHandler")
|
||||
}
|
||||
|
||||
pingConn := pingconn.New(conn)
|
||||
|
||||
// Cancel ping background goroutine when connection is closed.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
go h.startPing(ctx, pingConn)
|
||||
|
||||
return h.upgradeALPN(ctx, pingConn)
|
||||
}
|
||||
|
||||
type pingWriter interface {
|
||||
WritePing() error
|
||||
}
|
||||
@@ -203,21 +139,6 @@ func (h *Handler) startPing(ctx context.Context, pingConn pingWriter) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeUpgradeResponse(w io.Writer, upgradeType string) error {
|
||||
header := make(http.Header)
|
||||
header.Add(constants.WebAPIConnUpgradeHeader, upgradeType)
|
||||
header.Add(constants.WebAPIConnUpgradeTeleportHeader, upgradeType)
|
||||
header.Add(constants.WebAPIConnUpgradeConnectionHeader, constants.WebAPIConnUpgradeConnectionType)
|
||||
response := &http.Response{
|
||||
Status: http.StatusText(http.StatusSwitchingProtocols),
|
||||
StatusCode: http.StatusSwitchingProtocols,
|
||||
Header: header,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
}
|
||||
return response.Write(w)
|
||||
}
|
||||
|
||||
// waitConn is a net.Conn that provides a "WaitForClose" function to wait until
|
||||
// the connection is closed.
|
||||
type waitConn struct {
|
||||
|
||||
@@ -20,7 +20,6 @@ package web
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
@@ -37,26 +36,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/api/constants"
|
||||
"github.com/gravitational/teleport/api/utils/pingconn"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
"github.com/gravitational/teleport/lib/utils/listener"
|
||||
)
|
||||
|
||||
func TestWriteUpgradeResponse(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
require.NoError(t, writeUpgradeResponse(&buf, "custom"))
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(&buf), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Always drain/close the body.
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
require.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)
|
||||
require.Equal(t, "custom", resp.Header.Get("Upgrade"))
|
||||
}
|
||||
|
||||
func TestHandlerConnectionUpgrade(t *testing.T) {
|
||||
expectedPayload := "hello@"
|
||||
expectedIP := "1.2.3.4"
|
||||
@@ -114,38 +97,6 @@ func TestHandlerConnectionUpgrade(t *testing.T) {
|
||||
inputRequest: makeConnUpgradeRequest(t, "", "unsupported-protocol", expectedIP),
|
||||
checkHandlerError: trace.IsNotFound,
|
||||
},
|
||||
{
|
||||
// TODO(greedy52) DELETE in 17.0
|
||||
name: "upgraded to ALPN (legacy)",
|
||||
inputALPNHandler: simpleWriteHandler,
|
||||
inputRequest: makeConnUpgradeRequest(t, "", constants.WebAPIConnUpgradeTypeALPN, expectedIP),
|
||||
expectUpgradeType: constants.WebAPIConnUpgradeTypeALPN,
|
||||
checkClientConnString: mustReadClientConnString,
|
||||
},
|
||||
{
|
||||
// TODO(greedy52) DELETE in 17.0
|
||||
name: "upgraded to ALPN with Ping (legacy)",
|
||||
inputALPNHandler: simpleWriteHandler,
|
||||
inputRequest: makeConnUpgradeRequest(t, "", constants.WebAPIConnUpgradeTypeALPNPing, expectedIP),
|
||||
expectUpgradeType: constants.WebAPIConnUpgradeTypeALPNPing,
|
||||
wrapClientConn: toNetConn(pingconn.New),
|
||||
checkClientConnString: mustReadClientConnString,
|
||||
},
|
||||
{
|
||||
// TODO(greedy52) DELETE in 17.0
|
||||
name: "nested ALPN (legacy) upgrade",
|
||||
inputALPNHandler: nestedUpgradeHandler,
|
||||
inputRequest: makeConnUpgradeRequest(t, "", constants.WebAPIConnUpgradeTypeALPN, expectedIP),
|
||||
expectUpgradeType: constants.WebAPIConnUpgradeTypeALPN,
|
||||
checkClientConnString: mustWriteNestedWebSocketConnString,
|
||||
},
|
||||
{
|
||||
name: "upgraded to ALPN with Teleport-specific header",
|
||||
inputALPNHandler: simpleWriteHandler,
|
||||
inputRequest: makeConnUpgradeRequest(t, constants.WebAPIConnUpgradeTeleportHeader, constants.WebAPIConnUpgradeTypeALPN, expectedIP),
|
||||
expectUpgradeType: constants.WebAPIConnUpgradeTypeALPN,
|
||||
checkClientConnString: mustReadClientConnString,
|
||||
},
|
||||
{
|
||||
name: "upgraded to WebSocket",
|
||||
inputALPNHandler: simpleWriteHandler,
|
||||
@@ -336,9 +287,6 @@ func mustReadSwitchProtocolsResponse(t *testing.T, r *http.Request, clientConn n
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
|
||||
if upgradeType != "websocket" {
|
||||
require.Equal(t, upgradeType, resp.Header.Get(constants.WebAPIConnUpgradeTeleportHeader))
|
||||
}
|
||||
require.Equal(t, upgradeType, resp.Header.Get(constants.WebAPIConnUpgradeHeader))
|
||||
require.Equal(t, constants.WebAPIConnUpgradeConnectionType, resp.Header.Get(constants.WebAPIConnUpgradeConnectionHeader))
|
||||
require.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)
|
||||
|
||||
Reference in New Issue
Block a user