cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.1.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

carrier/carrier.go

157lines · modecode

1//Package carrier provides a WebSocket proxy to carry or proxy a connection
2//from the local client to the edge. See it as a wrapper around any protocol
3//that it packages up in a WebSocket connection to the edge.
4package carrier
5
6import (
7 "io"
8 "net"
9 "net/http"
10 "os"
11 "strings"
12
13 "github.com/cloudflare/cloudflared/cmd/cloudflared/token"
14 "github.com/cloudflare/cloudflared/h2mux"
15
16 "github.com/pkg/errors"
17 "github.com/rs/zerolog"
18)
19
20const LogFieldOriginURL = "originURL"
21
22type StartOptions struct {
23 OriginURL string
24 Headers http.Header
25}
26
27// Connection wraps up all the needed functions to forward over the tunnel
28type Connection interface {
29 // ServeStream is used to forward data from the client to the edge
30 ServeStream(*StartOptions, io.ReadWriter) error
31
32 // StartServer is used to listen for incoming connections from the edge to the origin
33 StartServer(net.Listener, string, <-chan struct{}) error
34}
35
36// StdinoutStream is empty struct for wrapping stdin/stdout
37// into a single ReadWriter
38type StdinoutStream struct {
39}
40
41// Read will read from Stdin
42func (c *StdinoutStream) Read(p []byte) (int, error) {
43 return os.Stdin.Read(p)
44
45}
46
47// Write will write to Stdout
48func (c *StdinoutStream) Write(p []byte) (int, error) {
49 return os.Stdout.Write(p)
50}
51
52// Helper to allow defering the response close with a check that the resp is not nil
53func closeRespBody(resp *http.Response) {
54 if resp != nil {
55 _ = resp.Body.Close()
56 }
57}
58
59// StartForwarder will setup a listener on a specified address/port and then
60// forward connections to the origin by calling `Serve()`.
61func StartForwarder(conn Connection, address string, shutdownC <-chan struct{}, options *StartOptions) error {
62 listener, err := net.Listen("tcp", address)
63 if err != nil {
64 return errors.Wrap(err, "failed to start forwarding server")
65 }
66 return Serve(conn, listener, shutdownC, options)
67}
68
69// StartClient will copy the data from stdin/stdout over a WebSocket connection
70// to the edge (originURL)
71func StartClient(conn Connection, stream io.ReadWriter, options *StartOptions) error {
72 return conn.ServeStream(options, stream)
73}
74
75// Serve accepts incoming connections on the specified net.Listener.
76// Each connection is handled in a new goroutine: its data is copied over a
77// WebSocket connection to the edge (originURL).
78// `Serve` always closes `listener`.
79func Serve(remoteConn Connection, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
80 defer listener.Close()
81 errChan := make(chan error)
82
83 go func() {
84 for {
85 conn, err := listener.Accept()
86 if err != nil {
87 // don't block if parent goroutine quit early
88 select {
89 case errChan <- err:
90 default:
91 }
92 return
93 }
94 go serveConnection(remoteConn, conn, options)
95 }
96 }()
97
98 select {
99 case <-shutdownC:
100 return nil
101 case err := <-errChan:
102 return err
103 }
104}
105
106// serveConnection handles connections for the Serve() call
107func serveConnection(remoteConn Connection, c net.Conn, options *StartOptions) {
108 defer c.Close()
109 _ = remoteConn.ServeStream(options, c)
110}
111
112// IsAccessResponse checks the http Response to see if the url location
113// contains the Access structure.
114func IsAccessResponse(resp *http.Response) bool {
115 if resp == nil || resp.StatusCode != http.StatusFound {
116 return false
117 }
118
119 location, err := resp.Location()
120 if err != nil || location == nil {
121 return false
122 }
123 if strings.HasPrefix(location.Path, "/cdn-cgi/access/login") {
124 return true
125 }
126
127 return false
128}
129
130// BuildAccessRequest builds an HTTP request with the Access token set
131func BuildAccessRequest(options *StartOptions, log *zerolog.Logger) (*http.Request, error) {
132 req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
133 if err != nil {
134 return nil, err
135 }
136
137 token, err := token.FetchTokenWithRedirect(req.URL, log)
138 if err != nil {
139 return nil, err
140 }
141
142 // We need to create a new request as FetchToken will modify req (boo mutable)
143 // as it has to follow redirect on the API and such, so here we init a new one
144 originRequest, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
145 if err != nil {
146 return nil, err
147 }
148 originRequest.Header.Set(h2mux.CFAccessTokenHeader, token)
149
150 for k, v := range options.Headers {
151 if len(v) >= 1 {
152 originRequest.Header.Set(k, v[0])
153 }
154 }
155
156 return originRequest, nil
157}
158