cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2022.9.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

carrier/websocket.go

205lines · modecode

1package carrier
2
3import (
4 "io"
5 "net/http"
6 "net/http/httputil"
7 "net/url"
8
9 "github.com/gorilla/websocket"
10 "github.com/rs/zerolog"
11
12 "github.com/cloudflare/cloudflared/token"
13 cfwebsocket "github.com/cloudflare/cloudflared/websocket"
14)
15
16// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
17// This implements the functions for glider proxy (sock5) and the carrier interface
18type Websocket struct {
19 log *zerolog.Logger
20 isSocks bool
21}
22
23// NewWSConnection returns a new connection object
24func NewWSConnection(log *zerolog.Logger) Connection {
25 return &Websocket{
26 log: log,
27 }
28}
29
30// ServeStream will create a Websocket client stream connection to the edge
31// it blocks and writes the raw data from conn over the tunnel
32func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) error {
33 wsConn, err := createWebsocketStream(options, ws.log)
34 if err != nil {
35 ws.log.Err(err).Str(LogFieldOriginURL, options.OriginURL).Msg("failed to connect to origin")
36 return err
37 }
38 defer wsConn.Close()
39
40 cfwebsocket.Stream(wsConn, conn, ws.log)
41 return nil
42}
43
44// createWebsocketStream will create a WebSocket connection to stream data over
45// It also handles redirects from Access and will present that flow if
46// the token is not present on the request
47func createWebsocketStream(options *StartOptions, log *zerolog.Logger) (*cfwebsocket.GorillaConn, error) {
48 req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
49 if err != nil {
50 return nil, err
51 }
52 req.Header = options.Headers
53 if options.Host != "" {
54 req.Host = options.Host
55 }
56
57 dump, err := httputil.DumpRequest(req, false)
58 if err != nil {
59 return nil, err
60 }
61 log.Debug().Msgf("Websocket request: %s", string(dump))
62
63 dialer := &websocket.Dialer{
64 TLSClientConfig: options.TLSClientConfig,
65 Proxy: http.ProxyFromEnvironment,
66 }
67 wsConn, resp, err := clientConnect(req, dialer)
68 defer closeRespBody(resp)
69
70 if err != nil && IsAccessResponse(resp) {
71 // Only get Access app info if we know the origin is protected by Access
72 originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
73 if err != nil {
74 return nil, err
75 }
76
77 appInfo, err := token.GetAppInfo(originReq.URL)
78 if err != nil {
79 return nil, err
80 }
81 options.AppInfo = appInfo
82
83 wsConn, err = createAccessAuthenticatedStream(options, log)
84 if err != nil {
85 return nil, err
86 }
87 } else if err != nil {
88 return nil, err
89 }
90
91 return &cfwebsocket.GorillaConn{Conn: wsConn}, nil
92}
93
94var stripWebsocketHeaders = []string{
95 "Upgrade",
96 "Connection",
97 "Sec-Websocket-Key",
98 "Sec-Websocket-Version",
99 "Sec-Websocket-Extensions",
100}
101
102// the gorilla websocket library sets its own Upgrade, Connection, Sec-WebSocket-Key,
103// Sec-WebSocket-Version and Sec-Websocket-Extensions headers.
104// https://github.com/gorilla/websocket/blob/master/client.go#L189-L194.
105func websocketHeaders(req *http.Request) http.Header {
106 wsHeaders := make(http.Header)
107 for key, val := range req.Header {
108 wsHeaders[key] = val
109 }
110 // Assume the header keys are in canonical format.
111 for _, header := range stripWebsocketHeaders {
112 wsHeaders.Del(header)
113 }
114 wsHeaders.Set("Host", req.Host) // See TUN-1097
115 return wsHeaders
116}
117
118// clientConnect creates a WebSocket client connection for provided request. Caller is responsible for closing
119// the connection. The response body may not contain the entire response and does
120// not need to be closed by the application.
121func clientConnect(req *http.Request, dialler *websocket.Dialer) (*websocket.Conn, *http.Response, error) {
122 req.URL.Scheme = changeRequestScheme(req.URL)
123 wsHeaders := websocketHeaders(req)
124 if dialler == nil {
125 dialler = &websocket.Dialer{
126 Proxy: http.ProxyFromEnvironment,
127 }
128 }
129 conn, response, err := dialler.Dial(req.URL.String(), wsHeaders)
130 if err != nil {
131 return nil, response, err
132 }
133 return conn, response, nil
134}
135
136// changeRequestScheme is needed as the gorilla websocket library requires the ws scheme.
137// (even though it changes it back to http/https, but ¯\_(ツ)_/¯.)
138func changeRequestScheme(reqURL *url.URL) string {
139 switch reqURL.Scheme {
140 case "https":
141 return "wss"
142 case "http":
143 return "ws"
144 case "":
145 return "ws"
146 default:
147 return reqURL.Scheme
148 }
149}
150
151// createAccessAuthenticatedStream will try load a token from storage and make
152// a connection with the token set on the request. If it still get redirect,
153// this probably means the token in storage is invalid (expired/revoked). If that
154// happens it deletes the token and runs the connection again, so the user can
155// login again and generate a new one.
156func createAccessAuthenticatedStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, error) {
157 wsConn, resp, err := createAccessWebSocketStream(options, log)
158 defer closeRespBody(resp)
159 if err == nil {
160 return wsConn, nil
161 }
162
163 if !IsAccessResponse(resp) {
164 return nil, err
165 }
166
167 // Access Token is invalid for some reason. Go through regen flow
168 if err := token.RemoveTokenIfExists(options.AppInfo); err != nil {
169 return nil, err
170 }
171 wsConn, resp, err = createAccessWebSocketStream(options, log)
172 defer closeRespBody(resp)
173 if err != nil {
174 return nil, err
175 }
176
177 return wsConn, nil
178}
179
180// createAccessWebSocketStream builds an Access request and makes a connection
181func createAccessWebSocketStream(options *StartOptions, log *zerolog.Logger) (*websocket.Conn, *http.Response, error) {
182 req, err := BuildAccessRequest(options, log)
183 if err != nil {
184 return nil, nil, err
185 }
186
187 dump, err := httputil.DumpRequest(req, false)
188 if err != nil {
189 return nil, nil, err
190 }
191 log.Debug().Msgf("Access Websocket request: %s", string(dump))
192
193 conn, resp, err := clientConnect(req, nil)
194
195 if resp != nil {
196 r, err := httputil.DumpResponse(resp, true)
197 if r != nil {
198 log.Debug().Msgf("Websocket response: %q", r)
199 } else if err != nil {
200 log.Debug().Msgf("Websocket response error: %v", err)
201 }
202 }
203
204 return conn, resp, err
205}
206