cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d03469b6d3467d2e79b741bdb9dc9bdfdacaaf6f

Branches

Tags

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

Clone

HTTPS

Download ZIP

cmd/cloudflared/transfer/transfer.go

162lines · modecode

1package transfer
2
3import (
4 "bytes"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "io/ioutil"
10 "net/http"
11 "net/url"
12 "os"
13 "time"
14
15 "github.com/cloudflare/cloudflared/cmd/cloudflared/encrypter"
16 "github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
17 "github.com/cloudflare/cloudflared/log"
18)
19
20const (
21 baseStoreURL = "https://login.argotunnel.com/"
22 clientTimeout = time.Second * 60
23)
24
25var logger = log.CreateLogger()
26
27// Run does the transfer "dance" with the end result downloading the supported resource.
28// The expanded description is run is encapsulation of shared business logic needed
29// to request a resource (token/cert/etc) from the transfer service (loginhelper).
30// The "dance" we refer to is building a HTTP request, opening that in a browser waiting for
31// the user to complete an action, while it long polls in the background waiting for an
32// action to be completed to download the resource.
33func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncrypt bool) ([]byte, error) {
34 encrypterClient, err := encrypter.New("cloudflared_priv.pem", "cloudflared_pub.pem")
35 if err != nil {
36 return nil, err
37 }
38 requestURL, err := buildRequestURL(transferURL, key, value+encrypterClient.PublicKey(), shouldEncrypt)
39 if err != nil {
40 return nil, err
41 }
42
43 // See AUTH-1423 for why we use stderr (the way git wraps ssh)
44 err = shell.OpenBrowser(requestURL)
45 if err != nil {
46 fmt.Fprintf(os.Stderr, "Please open the following URL and log in with your Cloudflare account:\n\n%s\n\nLeave cloudflared running to download the %s automatically.\n", requestURL, resourceName)
47 } else {
48 fmt.Fprintf(os.Stderr, "A browser window should have opened at the following URL:\n\n%s\n\nIf the browser failed to open, please visit the URL above directly in your browser.\n", requestURL)
49 }
50
51 var resourceData []byte
52
53 if shouldEncrypt {
54 buf, key, err := transferRequest(baseStoreURL + "transfer/" + encrypterClient.PublicKey())
55 if err != nil {
56 return nil, err
57 }
58
59 decodedBuf, err := base64.StdEncoding.DecodeString(string(buf))
60 if err != nil {
61 return nil, err
62 }
63 decrypted, err := encrypterClient.Decrypt(decodedBuf, key)
64 if err != nil {
65 return nil, err
66 }
67
68 resourceData = decrypted
69 } else {
70 buf, _, err := transferRequest(baseStoreURL + encrypterClient.PublicKey())
71 if err != nil {
72 return nil, err
73 }
74 resourceData = buf
75 }
76
77 if err := ioutil.WriteFile(path, resourceData, 0600); err != nil {
78 return nil, err
79 }
80
81 return resourceData, nil
82}
83
84// BuildRequestURL creates a request suitable for a resource transfer.
85// it will return a constructed url based off the base url and query key/value provided.
86// cli will build a url for cli transfer request.
87func buildRequestURL(baseURL *url.URL, key, value string, cli bool) (string, error) {
88 q := baseURL.Query()
89 q.Set(key, value)
90 baseURL.RawQuery = q.Encode()
91 if !cli {
92 return baseURL.String(), nil
93 }
94
95 q.Set("redirect_url", baseURL.String()) // we add the token as a query param on both the redirect_url
96 baseURL.RawQuery = q.Encode() // and this actual baseURL.
97 baseURL.Path = "cdn-cgi/access/cli"
98 return baseURL.String(), nil
99}
100
101// transferRequest downloads the requested resource from the request URL
102func transferRequest(requestURL string) ([]byte, string, error) {
103 client := &http.Client{Timeout: clientTimeout}
104 const pollAttempts = 10
105 // we do "long polling" on the endpoint to get the resource.
106 for i := 0; i < pollAttempts; i++ {
107 buf, key, err := poll(client, requestURL)
108 if err != nil {
109 return nil, "", err
110 } else if len(buf) > 0 {
111 if err := putSuccess(client, requestURL); err != nil {
112 logger.WithError(err).Error("Failed to update resource success")
113 }
114 return buf, key, nil
115 }
116 }
117 return nil, "", errors.New("Failed to fetch resource")
118}
119
120// poll the endpoint for the request resource, waiting for the user interaction
121func poll(client *http.Client, requestURL string) ([]byte, string, error) {
122 resp, err := client.Get(requestURL)
123 if err != nil {
124 return nil, "", err
125 }
126 defer resp.Body.Close()
127
128 // ignore everything other than server errors as the resource
129 // may not exist until the user does the interaction
130 if resp.StatusCode >= 500 {
131 return nil, "", fmt.Errorf("error on request %d", resp.StatusCode)
132 }
133 if resp.StatusCode != 200 {
134 logger.Info("Waiting for login...")
135 return nil, "", nil
136 }
137
138 buf := new(bytes.Buffer)
139 if _, err := io.Copy(buf, resp.Body); err != nil {
140 return nil, "", err
141 }
142 return buf.Bytes(), resp.Header.Get("service-public-key"), nil
143}
144
145// putSuccess tells the server we successfully downloaded the resource
146func putSuccess(client *http.Client, requestURL string) error {
147 req, err := http.NewRequest("PUT", requestURL+"/ok", nil)
148 if err != nil {
149 return err
150 }
151
152 resp, err := client.Do(req)
153 if err != nil {
154 return err
155 }
156
157 resp.Body.Close()
158 if resp.StatusCode != 200 {
159 return fmt.Errorf("HTTP Response Status Code: %d", resp.StatusCode)
160 }
161 return nil
162}
163