-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathreverse.go
More file actions
281 lines (257 loc) · 7.75 KB
/
Copy pathreverse.go
File metadata and controls
281 lines (257 loc) · 7.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package main
import (
"bufio"
"crypto/hmac"
"crypto/md5"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"
"time"
"golang.org/x/build"
"golang.org/x/build/revdial"
)
// mode is either a BuildConfig or HostConfig name (map key in x/build/dashboard/builders.go)
func keyForMode(mode string) (string, error) {
if isDevReverseMode() {
return string(devBuilderKey(mode)), nil
}
if os.Getenv("GO_BUILDER_ENV") == "macstadium_vm" {
infoKey := "guestinfo.key-" + mode
key := vmwareGetInfo(infoKey)
if key == "" {
return "", fmt.Errorf("no build key found for VMWare info-get key %q", infoKey)
}
return key, nil
}
keyPath := filepath.Join(homedir(), ".gobuildkey-"+mode)
if v := os.Getenv("GO_BUILD_KEY_PATH"); v != "" {
keyPath = v
}
key, err := ioutil.ReadFile(keyPath)
if ok, _ := strconv.ParseBool(os.Getenv("GO_BUILD_KEY_DELETE_AFTER_READ")); ok {
os.Remove(keyPath)
}
if err != nil {
if os.IsNotExist(err) && *reverse != "" && !strings.Contains(*reverse, ",") {
globalKeyPath := filepath.Join(homedir(), ".gobuildkey")
key, err = ioutil.ReadFile(globalKeyPath)
if err != nil {
return "", fmt.Errorf("cannot read either key file %q or %q: %v", keyPath, globalKeyPath, err)
}
}
if len(key) == 0 || err != nil {
return "", fmt.Errorf("cannot read key file %q: %v", keyPath, err)
}
}
return string(key), nil
}
func isDevReverseMode() bool {
return !strings.HasPrefix(*coordinator, "farmer.golang.org")
}
func dialCoordinator() error {
devMode := isDevReverseMode()
if *hostname == "" {
*hostname, _ = os.Hostname()
}
var modes, keys []string
if *reverse != "" {
// Old way.
modes = strings.Split(*reverse, ",")
for _, m := range modes {
key, err := keyForMode(m)
if err != nil {
log.Fatalf("failed to find key for %s: %v", m, err)
}
keys = append(keys, key)
}
} else {
// New way.
key, err := keyForMode(*reverseType)
if err != nil {
log.Fatalf("failed to find key for %s: %v", *reverseType, err)
}
keys = append(keys, key)
}
caCert := build.ProdCoordinatorCA
addr := *coordinator
if addr == "farmer.golang.org" {
addr = "farmer.golang.org:443"
}
if devMode {
caCert = build.DevCoordinatorCA
}
var caPool *x509.CertPool
if runtime.GOOS == "windows" {
// No SystemCertPool on Windows. But we don't run
// Windows in reverse mode anyway. So just don't set
// caPool, which will cause tls.Config to use the
// system verification.
} else {
var err error
caPool, err = x509.SystemCertPool()
if err != nil {
return fmt.Errorf("SystemCertPool: %v", err)
}
// Temporarily accept our own CA. This predates LetsEncrypt.
// Our old self-signed cert expires April 4th, 2017.
// We can remove this after golang.org/issue/16442 is fixed.
if !caPool.AppendCertsFromPEM([]byte(caCert)) {
return errors.New("failed to append coordinator CA certificate")
}
}
log.Printf("Dialing coordinator %s ...", addr)
tcpConn, err := dialCoordinatorTCP(addr)
if err != nil {
return err
}
serverName := strings.TrimSuffix(addr, ":443")
log.Printf("Doing TLS handshake with coordinator (verifying hostname %q)...", serverName)
tcpConn.SetDeadline(time.Now().Add(30 * time.Second))
config := &tls.Config{
ServerName: serverName,
RootCAs: caPool,
InsecureSkipVerify: devMode,
}
conn := tls.Client(tcpConn, config)
if err := conn.Handshake(); err != nil {
return fmt.Errorf("failed to handshake with coordinator: %v", err)
}
tcpConn.SetDeadline(time.Time{})
bufr := bufio.NewReader(conn)
log.Printf("Registering reverse mode with coordinator...")
req, err := http.NewRequest("GET", "/reverse", nil)
if err != nil {
log.Fatal(err)
}
if *reverse != "" {
// Old way.
req.Header["X-Go-Builder-Type"] = modes
} else {
req.Header.Set("X-Go-Host-Type", *reverseType)
}
req.Header["X-Go-Builder-Key"] = keys
req.Header.Set("X-Go-Builder-Hostname", *hostname)
req.Header.Set("X-Go-Builder-Version", strconv.Itoa(buildletVersion))
if err := req.Write(conn); err != nil {
return fmt.Errorf("coordinator /reverse request failed: %v", err)
}
resp, err := http.ReadResponse(bufr, req)
if err != nil {
return fmt.Errorf("coordinator /reverse response failed: %v", err)
}
if resp.StatusCode != 101 {
msg, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("coordinator registration failed; want HTTP status 101; got %v:\n\t%s", resp.Status, msg)
}
log.Printf("Connected to coordinator; reverse dialing active")
srv := &http.Server{}
ln := revdial.NewListener(bufio.NewReadWriter(
bufio.NewReader(conn),
bufio.NewWriter(deadlinePerWriteConn{conn, 60 * time.Second}),
))
err = srv.Serve(ln)
if ln.Closed() {
return nil
}
return fmt.Errorf("http.Serve on reverse connection complete: %v", err)
}
var coordDialer = &net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 15 * time.Second,
}
// dialCoordinatorTCP returns a TCP connection to the coordinator, making
// a CONNECT request to a proxy as a fallback.
func dialCoordinatorTCP(addr string) (net.Conn, error) {
tcpConn, err := coordDialer.Dial("tcp", addr)
if err != nil {
// If we had problems connecting to the TCP addr
// directly, perhaps there's a proxy in the way. See
// if they have an HTTPS_PROXY environment variable
// defined and try to do a CONNECT request to it.
req, _ := http.NewRequest("GET", "https://"+addr, nil)
proxyURL, _ := http.ProxyFromEnvironment(req)
if proxyURL != nil {
return dialCoordinatorViaCONNECT(addr, proxyURL)
}
return nil, err
}
return tcpConn, nil
}
func dialCoordinatorViaCONNECT(addr string, proxy *url.URL) (net.Conn, error) {
proxyAddr := proxy.Host
if proxy.Port() == "" {
proxyAddr = net.JoinHostPort(proxyAddr, "80")
}
log.Printf("dialing proxy %q ...", proxyAddr)
c, err := net.Dial("tcp", proxyAddr)
if err != nil {
return nil, fmt.Errorf("dialing proxy %q failed: %v", proxyAddr, err)
}
fmt.Fprintf(c, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", addr, proxy.Hostname())
br := bufio.NewReader(c)
res, err := http.ReadResponse(br, nil)
if err != nil {
return nil, fmt.Errorf("reading HTTP response from CONNECT to %s via proxy %s failed: %v",
addr, proxyAddr, err)
}
if res.StatusCode != 200 {
return nil, fmt.Errorf("proxy error from %s while dialing %s: %v", proxyAddr, addr, res.Status)
}
// It's safe to discard the bufio.Reader here and return the
// original TCP conn directly because we only use this for
// TLS, and in TLS the client speaks first, so we know there's
// no unbuffered data. But we can double-check.
if br.Buffered() > 0 {
return nil, fmt.Errorf("unexpected %d bytes of buffered data from CONNECT proxy %q",
br.Buffered(), proxyAddr)
}
return c, nil
}
type deadlinePerWriteConn struct {
net.Conn
writeTimeout time.Duration
}
func (c deadlinePerWriteConn) Write(p []byte) (n int, err error) {
c.Conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
defer c.Conn.SetWriteDeadline(time.Time{})
return c.Conn.Write(p)
}
func devBuilderKey(builder string) string {
h := hmac.New(md5.New, []byte("gophers rule"))
io.WriteString(h, builder)
return fmt.Sprintf("%x", h.Sum(nil))
}
func homedir() string {
if runtime.GOOS == "windows" {
return os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
}
home := os.Getenv("HOME")
if home != "" {
return home
}
if os.Getuid() == 0 {
return "/root"
}
return "/"
}
// TestDialCoordinator dials the coordinator. Exported for testing.
func TestDialCoordinator() {
// TODO(crawshaw): move some of this logic out of main to simplify testing hook.
http.Handle("/status", http.HandlerFunc(handleStatus))
dialCoordinator()
}