mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-11 21:56:34 +00:00

All header transformation code from h2mux has been consolidated in the connection package since it's used by both h2mux and http2 logic. Exported headers used by proxying between edge and cloudflared so then can be shared by tunnel service on the edge. Moved access-related headers to corresponding packages that have the code that sets/uses these headers. Removed tunnel hostname tracking from h2mux since it wasn't used by anything. We will continue to set the tunnel hostname header from the edge for backward compatibilty, but it's no longer used by cloudflared. Move bastion-related logic into carrier package, untangled dependencies between carrier, origin, and websocket packages.
142 lines
4.0 KiB
Go
142 lines
4.0 KiB
Go
package carrier
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
|
|
"github.com/gorilla/websocket"
|
|
"github.com/rs/zerolog"
|
|
|
|
"github.com/cloudflare/cloudflared/token"
|
|
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
|
|
)
|
|
|
|
// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
|
|
// This implements the functions for glider proxy (sock5) and the carrier interface
|
|
type Websocket struct {
|
|
log *zerolog.Logger
|
|
isSocks bool
|
|
}
|
|
|
|
// NewWSConnection returns a new connection object
|
|
func NewWSConnection(log *zerolog.Logger) Connection {
|
|
return &Websocket{
|
|
log: log,
|
|
}
|
|
}
|
|
|
|
// ServeStream will create a Websocket client stream connection to the edge
|
|
// it blocks and writes the raw data from conn over the tunnel
|
|
func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) error {
|
|
wsConn, err := createWebsocketStream(options, ws.log)
|
|
if err != nil {
|
|
ws.log.Err(err).Str(LogFieldOriginURL, options.OriginURL).Msg("failed to connect to origin")
|
|
return err
|
|
}
|
|
defer wsConn.Close()
|
|
|
|
cfwebsocket.Stream(wsConn, conn, ws.log)
|
|
return nil
|
|
}
|
|
|
|
// createWebsocketStream will create a WebSocket connection to stream data over
|
|
// It also handles redirects from Access and will present that flow if
|
|
// the token is not present on the request
|
|
func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebsocket.GorillaConn, error) {
|
|
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header = options.Headers
|
|
if options.Host != "" {
|
|
req.Host = options.Host
|
|
}
|
|
|
|
dump, err := httputil.DumpRequest(req, false)
|
|
log.Debug().Msgf("Websocket request: %s", string(dump))
|
|
|
|
dialer := &websocket.Dialer{
|
|
TLSClientConfig: options.TLSClientConfig,
|
|
Proxy: http.ProxyFromEnvironment,
|
|
}
|
|
wsConn, resp, err := cfwebsocket.ClientConnect(req, dialer)
|
|
defer closeRespBody(resp)
|
|
|
|
if err != nil && IsAccessResponse(resp) {
|
|
// Only get Access app info if we know the origin is protected by Access
|
|
originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
appInfo, err := token.GetAppInfo(originReq.URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
options.AppInfo = appInfo
|
|
|
|
wsConn, err = createAccessAuthenticatedStream(options, log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &cfwebsocket.GorillaConn{Conn: wsConn}, nil
|
|
}
|
|
|
|
// createAccessAuthenticatedStream will try load a token from storage and make
|
|
// a connection with the token set on the request. If it still get redirect,
|
|
// this probably means the token in storage is invalid (expired/revoked). If that
|
|
// happens it deletes the token and runs the connection again, so the user can
|
|
// login again and generate a new one.
|
|
func createAccessAuthenticatedStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, error) {
|
|
wsConn, resp, err := createAccessWebSocketStream(options, log)
|
|
defer closeRespBody(resp)
|
|
if err == nil {
|
|
return wsConn, nil
|
|
}
|
|
|
|
if !IsAccessResponse(resp) {
|
|
return nil, err
|
|
}
|
|
|
|
// Access Token is invalid for some reason. Go through regen flow
|
|
if err := token.RemoveTokenIfExists(options.AppInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
wsConn, resp, err = createAccessWebSocketStream(options, log)
|
|
defer closeRespBody(resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return wsConn, nil
|
|
}
|
|
|
|
// createAccessWebSocketStream builds an Access request and makes a connection
|
|
func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, *http.Response, error) {
|
|
req, err := BuildAccessRequest(options, log)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
dump, err := httputil.DumpRequest(req, false)
|
|
log.Debug().Msgf("Access Websocket request: %s", string(dump))
|
|
|
|
conn, resp, err := cfwebsocket.ClientConnect(req, nil)
|
|
|
|
if resp != nil {
|
|
r, err := httputil.DumpResponse(resp, true)
|
|
if r != nil {
|
|
log.Debug().Msgf("Websocket response: %q", r)
|
|
} else if err != nil {
|
|
log.Debug().Msgf("Websocket response error: %v", err)
|
|
}
|
|
}
|
|
|
|
return conn, resp, err
|
|
}
|