2022-02-07 09:42:07 +00:00
package proxy
2020-10-08 10:12:26 +00:00
import (
2020-10-20 15:26:55 +00:00
"context"
2020-11-02 11:21:34 +00:00
"fmt"
2020-10-08 10:12:26 +00:00
"io"
"net/http"
"strconv"
2024-02-07 14:32:39 +00:00
"time"
2020-10-08 10:12:26 +00:00
2021-03-08 16:46:23 +00:00
"github.com/pkg/errors"
"github.com/rs/zerolog"
2022-04-06 23:20:29 +00:00
"go.opentelemetry.io/otel/attribute"
2022-04-11 23:02:13 +00:00
"go.opentelemetry.io/otel/trace"
2021-03-08 16:46:23 +00:00
2021-07-01 18:30:26 +00:00
"github.com/cloudflare/cloudflared/carrier"
2022-04-11 16:58:18 +00:00
"github.com/cloudflare/cloudflared/cfio"
2024-05-09 19:07:59 +00:00
"github.com/cloudflare/cloudflared/config"
2020-10-08 10:12:26 +00:00
"github.com/cloudflare/cloudflared/connection"
2020-11-02 11:21:34 +00:00
"github.com/cloudflare/cloudflared/ingress"
2022-12-25 04:05:51 +00:00
"github.com/cloudflare/cloudflared/stream"
2022-04-06 23:20:29 +00:00
"github.com/cloudflare/cloudflared/tracing"
2020-10-08 10:12:26 +00:00
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
)
const (
2021-07-16 15:14:37 +00:00
// TagHeaderNamePrefix indicates a Cloudflared Warp Tag prefix that gets appended for warp traffic stream headers.
2024-02-16 01:25:48 +00:00
TagHeaderNamePrefix = "Cf-Warp-Tag-"
trailerHeaderName = "Trailer"
2020-10-08 10:12:26 +00:00
)
2021-07-16 15:14:37 +00:00
// Proxy represents a means to Proxy between cloudflared and the origin services.
type Proxy struct {
2022-02-11 10:49:06 +00:00
ingressRules ingress . Ingress
2021-01-17 20:22:53 +00:00
warpRouting * ingress . WarpRoutingService
2023-03-21 18:42:25 +00:00
management * ingress . ManagementService
2020-11-02 11:21:34 +00:00
tags [ ] tunnelpogs . Tag
2020-11-25 06:55:13 +00:00
log * zerolog . Logger
2020-10-08 10:12:26 +00:00
}
2021-07-16 15:14:37 +00:00
// NewOriginProxy returns a new instance of the Proxy struct.
2021-01-17 20:22:53 +00:00
func NewOriginProxy (
2022-02-11 10:49:06 +00:00
ingressRules ingress . Ingress ,
2022-06-13 16:44:27 +00:00
warpRouting ingress . WarpRoutingConfig ,
2021-01-17 20:22:53 +00:00
tags [ ] tunnelpogs . Tag ,
2024-02-12 18:58:55 +00:00
writeTimeout time . Duration ,
2021-07-16 15:14:37 +00:00
log * zerolog . Logger ,
) * Proxy {
2022-02-11 10:49:06 +00:00
proxy := & Proxy {
2020-11-02 11:21:34 +00:00
ingressRules : ingressRules ,
tags : tags ,
2020-11-25 06:55:13 +00:00
log : log ,
2020-10-08 10:12:26 +00:00
}
2023-09-08 17:05:13 +00:00
2024-02-12 18:58:55 +00:00
proxy . warpRouting = ingress . NewWarpRoutingService ( warpRouting , writeTimeout )
2022-02-11 10:49:06 +00:00
return proxy
2020-10-08 10:12:26 +00:00
}
2022-09-22 14:11:59 +00:00
func ( p * Proxy ) applyIngressMiddleware ( rule * ingress . Rule , r * http . Request , w connection . ResponseWriter ) ( error , bool ) {
for _ , handler := range rule . Handlers {
result , err := handler . Handle ( r . Context ( ) , r )
if err != nil {
return errors . Wrap ( err , fmt . Sprintf ( "error while processing middleware handler %s" , handler . Name ( ) ) ) , false
}
if result . ShouldFilterRequest {
w . WriteRespHeaders ( result . StatusCode , nil )
return fmt . Errorf ( "request filtered by middleware handler (%s) due to: %s" , handler . Name ( ) , result . Reason ) , true
}
}
return nil , true
}
2021-07-16 15:14:37 +00:00
// ProxyHTTP further depends on ingress rules to establish a connection with the origin service. This may be
// a simple roundtrip or a tcp/websocket dial depending on ingres rule setup.
func ( p * Proxy ) ProxyHTTP (
w connection . ResponseWriter ,
2022-07-26 21:00:53 +00:00
tr * tracing . TracedHTTPRequest ,
2021-07-16 15:14:37 +00:00
isWebsocket bool ,
) error {
2020-10-08 10:12:26 +00:00
incrementRequests ( )
defer decrementConcurrentRequests ( )
2022-04-06 23:20:29 +00:00
req := tr . Request
2021-08-23 15:04:09 +00:00
p . appendTagHeaders ( req )
2021-01-17 20:22:53 +00:00
2022-04-11 23:02:13 +00:00
_ , ruleSpan := tr . Tracer ( ) . Start ( req . Context ( ) , "ingress_match" ,
trace . WithAttributes ( attribute . String ( "req-host" , req . Host ) ) )
2024-05-09 19:07:59 +00:00
rule , ruleNum := p . ingressRules . FindMatchingRule ( req . Host , req . URL . Path , req . Header . Get ( carrier . CFJumpDestinationHeader ) )
2022-04-06 23:20:29 +00:00
ruleSpan . SetAttributes ( attribute . Int ( "rule-num" , ruleNum ) )
ruleSpan . End ( )
2024-02-16 01:25:48 +00:00
logger := newHTTPLogger ( p . log , tr . ConnIndex , req , ruleNum , rule . Service . String ( ) )
logHTTPRequest ( & logger , req )
2022-09-22 14:11:59 +00:00
if err , applied := p . applyIngressMiddleware ( rule , req , w ) ; err != nil {
if applied {
2024-02-16 01:25:48 +00:00
logRequestError ( & logger , err )
2022-09-22 14:11:59 +00:00
return nil
}
return err
}
2024-05-09 19:07:59 +00:00
// Handling for StreamBasedOriginProxy or BastionMode
if _ , ok := rule . Service . ( ingress . StreamBasedOriginProxy ) ; ok || rule . Config . BastionMode {
if _ , ok := rule . Service . ( ingress . StreamBasedOriginProxy ) ; ! ok && rule . Config . BastionMode {
return fmt . Errorf ( "Unrecognized service: %s" , rule . Service )
2021-01-11 19:59:45 +00:00
}
2024-05-09 19:07:59 +00:00
2021-07-01 18:30:26 +00:00
dest , err := getDestFromRule ( rule , req )
if err != nil {
return err
}
2024-05-09 19:07:59 +00:00
2023-07-06 13:42:44 +00:00
flusher , ok := w . ( http . Flusher )
if ! ok {
return fmt . Errorf ( "response writer is not a flusher" )
}
rws := connection . NewHTTPResponseReadWriterAcker ( w , flusher , req )
2024-02-16 01:25:48 +00:00
logger := logger . With ( ) . Str ( logFieldDestAddr , dest ) . Logger ( )
2024-05-09 19:07:59 +00:00
if err := p . proxyStream ( tr . ToTracedContext ( ) , rws , dest , rule . Service . ( ingress . StreamBasedOriginProxy ) , & logger ) ; err != nil {
logRequestError ( & logger , err )
return err
}
return nil
}
switch originProxy := rule . Service . ( type ) {
case ingress . HTTPOriginProxy :
if err := p . proxyHTTPRequest (
w ,
tr ,
originProxy ,
isWebsocket ,
rule . Config . DisableChunkedEncoding ,
& logger ,
) ; err != nil {
2024-02-16 01:25:48 +00:00
logRequestError ( & logger , err )
2021-07-01 09:29:53 +00:00
return err
}
return nil
2023-03-21 18:42:25 +00:00
case ingress . HTTPLocalProxy :
2023-04-04 22:45:32 +00:00
p . proxyLocalRequest ( originProxy , w , req , isWebsocket )
2023-03-21 18:42:25 +00:00
return nil
2021-07-01 09:29:53 +00:00
default :
return fmt . Errorf ( "Unrecognized service: %s, %t" , rule . Service , originProxy )
2021-07-01 18:30:26 +00:00
}
}
2021-02-02 18:27:50 +00:00
2021-07-16 15:14:37 +00:00
// ProxyTCP proxies to a TCP connection between the origin service and cloudflared.
func ( p * Proxy ) ProxyTCP (
ctx context . Context ,
rwa connection . ReadWriteAcker ,
req * connection . TCPRequest ,
) error {
2023-06-17 00:07:56 +00:00
incrementTCPRequests ( )
defer decrementTCPConcurrentRequests ( )
2021-07-16 15:14:37 +00:00
if p . warpRouting == nil {
err := errors . New ( ` cloudflared received a request from WARP client, but your configuration has disabled ingress from WARP clients. To enable this, set "warp-routing:\n\t enabled: true" in your config.yaml ` )
p . log . Error ( ) . Msg ( err . Error ( ) )
return err
2021-07-01 18:30:26 +00:00
}
2021-07-16 15:14:37 +00:00
serveCtx , cancel := context . WithCancel ( ctx )
defer cancel ( )
2024-02-16 01:25:48 +00:00
logger := newTCPLogger ( p . log , req )
tracedCtx := tracing . NewTracedContext ( serveCtx , req . CfTraceID , & logger )
2024-02-09 15:56:56 +00:00
logger . Debug ( ) . Msg ( "tcp proxy stream started" )
2022-06-13 16:44:27 +00:00
2024-02-16 01:25:48 +00:00
if err := p . proxyStream ( tracedCtx , rwa , req . Dest , p . warpRouting . Proxy , & logger ) ; err != nil {
logRequestError ( & logger , err )
2021-07-16 15:14:37 +00:00
return err
2021-07-01 18:30:26 +00:00
}
2021-07-16 15:14:37 +00:00
2024-02-09 15:56:56 +00:00
logger . Debug ( ) . Msg ( "tcp proxy stream finished successfully" )
2022-06-09 12:55:26 +00:00
2021-07-16 15:14:37 +00:00
return nil
2021-01-17 20:22:53 +00:00
}
2021-07-16 15:14:37 +00:00
// ProxyHTTPRequest proxies requests of underlying type http and websocket to the origin service.
func ( p * Proxy ) proxyHTTPRequest (
2021-07-01 09:29:53 +00:00
w connection . ResponseWriter ,
2022-07-26 21:00:53 +00:00
tr * tracing . TracedHTTPRequest ,
2021-07-01 09:29:53 +00:00
httpService ingress . HTTPOriginProxy ,
isWebsocket bool ,
disableChunkedEncoding bool ,
2024-02-16 01:25:48 +00:00
logger * zerolog . Logger ,
2021-07-16 15:14:37 +00:00
) error {
2022-04-11 19:57:50 +00:00
roundTripReq := tr . Request
2021-07-01 09:29:53 +00:00
if isWebsocket {
2022-04-11 23:02:13 +00:00
roundTripReq = tr . Clone ( tr . Request . Context ( ) )
2021-07-01 09:29:53 +00:00
roundTripReq . Header . Set ( "Connection" , "Upgrade" )
roundTripReq . Header . Set ( "Upgrade" , "websocket" )
roundTripReq . Header . Set ( "Sec-Websocket-Version" , "13" )
roundTripReq . ContentLength = 0
roundTripReq . Body = nil
} else {
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
if disableChunkedEncoding {
roundTripReq . TransferEncoding = [ ] string { "gzip" , "deflate" }
2022-04-11 19:57:50 +00:00
cLength , err := strconv . Atoi ( tr . Request . Header . Get ( "Content-Length" ) )
2021-07-01 09:29:53 +00:00
if err == nil {
roundTripReq . ContentLength = int64 ( cLength )
}
2020-10-08 10:12:26 +00:00
}
2021-07-01 09:29:53 +00:00
// Request origin to keep connection alive to improve performance
roundTripReq . Header . Set ( "Connection" , "keep-alive" )
2020-10-08 10:12:26 +00:00
}
2021-11-13 00:34:19 +00:00
// Set the User-Agent as an empty string if not provided to avoid inserting golang default UA
if roundTripReq . Header . Get ( "User-Agent" ) == "" {
roundTripReq . Header . Set ( "User-Agent" , "" )
}
2022-04-11 23:02:13 +00:00
_ , ttfbSpan := tr . Tracer ( ) . Start ( tr . Context ( ) , "ttfb_origin" )
2021-07-01 09:29:53 +00:00
resp , err := httpService . RoundTrip ( roundTripReq )
2020-10-08 10:12:26 +00:00
if err != nil {
2022-05-18 11:11:38 +00:00
tracing . EndWithErrorStatus ( ttfbSpan , err )
2022-06-17 21:39:38 +00:00
if err := roundTripReq . Context ( ) . Err ( ) ; err != nil {
return errors . Wrap ( err , "Incoming request ended abruptly" )
}
2021-05-15 04:49:34 +00:00
return errors . Wrap ( err , "Unable to reach the origin service. The service may be down or it may not be responding to traffic from cloudflared" )
2020-10-08 10:12:26 +00:00
}
2022-05-18 11:11:38 +00:00
tracing . EndWithStatusCode ( ttfbSpan , resp . StatusCode )
2020-10-08 10:12:26 +00:00
defer resp . Body . Close ( )
2022-08-16 11:21:58 +00:00
headers := make ( http . Header , len ( resp . Header ) )
// copy headers
for k , v := range resp . Header {
headers [ k ] = v
2022-05-18 10:11:48 +00:00
}
2022-04-11 19:57:50 +00:00
// Add spans to response header (if available)
2022-08-16 11:21:58 +00:00
tr . AddSpans ( headers )
2022-04-11 19:57:50 +00:00
2022-08-16 11:21:58 +00:00
err = w . WriteRespHeaders ( resp . StatusCode , headers )
2020-10-08 10:12:26 +00:00
if err != nil {
2021-02-05 13:01:53 +00:00
return errors . Wrap ( err , "Error writing response header" )
2020-10-08 10:12:26 +00:00
}
2021-07-01 09:29:53 +00:00
if resp . StatusCode == http . StatusSwitchingProtocols {
rwc , ok := resp . Body . ( io . ReadWriteCloser )
if ! ok {
return errors . New ( "internal error: unsupported connection type" )
}
defer rwc . Close ( )
eyeballStream := & bidirectionalStream {
writer : w ,
2022-04-11 19:57:50 +00:00
reader : tr . Request . Body ,
2021-07-01 09:29:53 +00:00
}
2024-02-16 01:25:48 +00:00
stream . Pipe ( eyeballStream , rwc , logger )
2021-07-01 09:29:53 +00:00
return nil
}
2023-01-16 12:42:59 +00:00
if _ , err = cfio . Copy ( w , resp . Body ) ; err != nil {
return err
}
2022-08-16 11:21:58 +00:00
// copy trailers
copyTrailers ( w , resp )
2021-07-16 15:14:37 +00:00
2024-02-16 01:25:48 +00:00
logOriginHTTPResponse ( logger , resp )
2021-02-05 13:01:53 +00:00
return nil
2020-10-08 10:12:26 +00:00
}
2021-07-16 15:14:37 +00:00
// proxyStream proxies type TCP and other underlying types if the connection is defined as a stream oriented
// ingress rule.
2024-02-07 14:32:39 +00:00
// connectedLogger is used to log when the connection is acknowledged
2021-07-16 15:14:37 +00:00
func ( p * Proxy ) proxyStream (
2022-07-26 21:00:53 +00:00
tr * tracing . TracedContext ,
2021-07-16 15:14:37 +00:00
rwa connection . ReadWriteAcker ,
2021-07-01 18:30:26 +00:00
dest string ,
2021-02-02 18:27:50 +00:00
connectionProxy ingress . StreamBasedOriginProxy ,
2024-02-16 01:25:48 +00:00
logger * zerolog . Logger ,
2021-02-05 13:01:53 +00:00
) error {
2022-07-26 21:00:53 +00:00
ctx := tr . Context
2022-08-11 21:54:12 +00:00
_ , connectSpan := tr . Tracer ( ) . Start ( ctx , "stream-connect" )
2024-02-07 14:32:39 +00:00
start := time . Now ( )
2024-02-19 12:41:38 +00:00
originConn , err := connectionProxy . EstablishConnection ( ctx , dest , logger )
2020-10-08 10:12:26 +00:00
if err != nil {
2024-02-07 14:32:39 +00:00
connectStreamErrors . Inc ( )
2022-07-26 21:00:53 +00:00
tracing . EndWithErrorStatus ( connectSpan , err )
2021-02-05 13:01:53 +00:00
return err
2021-02-02 18:27:50 +00:00
}
2022-07-26 21:00:53 +00:00
connectSpan . End ( )
2023-01-30 22:45:42 +00:00
defer originConn . Close ( )
2024-02-09 15:56:56 +00:00
logger . Debug ( ) . Msg ( "origin connection established" )
2022-07-26 21:00:53 +00:00
encodedSpans := tr . GetSpans ( )
2021-07-01 18:30:26 +00:00
2022-07-26 21:00:53 +00:00
if err := rwa . AckConnection ( encodedSpans ) ; err != nil {
2024-02-07 14:32:39 +00:00
connectStreamErrors . Inc ( )
2021-02-05 13:01:53 +00:00
return err
2021-02-02 18:27:50 +00:00
}
2024-02-07 14:32:39 +00:00
connectLatency . Observe ( float64 ( time . Since ( start ) . Milliseconds ( ) ) )
2024-02-09 15:56:56 +00:00
logger . Debug ( ) . Msg ( "proxy stream acknowledged" )
2024-02-07 14:32:39 +00:00
2024-02-16 01:25:48 +00:00
originConn . Stream ( ctx , rwa , logger )
2021-02-05 13:01:53 +00:00
return nil
2020-10-08 10:12:26 +00:00
}
2023-04-04 22:45:32 +00:00
func ( p * Proxy ) proxyLocalRequest ( proxy ingress . HTTPLocalProxy , w connection . ResponseWriter , req * http . Request , isWebsocket bool ) {
if isWebsocket {
// These headers are added since they are stripped off during an eyeball request to origintunneld, but they
// are required during the Handshake process of a WebSocket request.
req . Header . Set ( "Connection" , "Upgrade" )
req . Header . Set ( "Upgrade" , "websocket" )
req . Header . Set ( "Sec-Websocket-Version" , "13" )
}
proxy . ServeHTTP ( w , req )
}
2021-02-11 14:36:42 +00:00
type bidirectionalStream struct {
reader io . Reader
writer io . Writer
}
func ( wr * bidirectionalStream ) Read ( p [ ] byte ) ( n int , err error ) {
return wr . reader . Read ( p )
}
func ( wr * bidirectionalStream ) Write ( p [ ] byte ) ( n int , err error ) {
return wr . writer . Write ( p )
}
2021-07-16 15:14:37 +00:00
func ( p * Proxy ) appendTagHeaders ( r * http . Request ) {
2020-12-09 21:46:53 +00:00
for _ , tag := range p . tags {
2020-10-08 10:12:26 +00:00
r . Header . Add ( TagHeaderNamePrefix + tag . Name , tag . Value )
}
}
2022-08-16 11:21:58 +00:00
func copyTrailers ( w connection . ResponseWriter , response * http . Response ) {
for trailerHeader , trailerValues := range response . Trailer {
for _ , trailerValue := range trailerValues {
w . AddTrailer ( trailerHeader , trailerValue )
}
}
}
2021-07-16 15:14:37 +00:00
func getDestFromRule ( rule * ingress . Rule , req * http . Request ) ( string , error ) {
2024-05-09 19:07:59 +00:00
if rule . Config . BastionMode || rule . Service . String ( ) == config . BastionFlag {
return carrier . ResolveBastionDest ( req , rule . Config . BastionMode , rule . Service . String ( ) )
} else {
2021-07-16 15:14:37 +00:00
return rule . Service . String ( ) , nil
}
2020-10-08 10:12:26 +00:00
}