blob: 844d8a4ca90f87d820630e3b1a1a432866481280 [file] [log] [blame]
// 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"
"os"
"path/filepath"
"runtime"
"strings"
"time"
)
func repeatDialCoordinator() {
for {
if err := dialCoordinator(); err != nil {
log.Print(err)
}
log.Printf("Waiting 30 seconds and dialing again.")
time.Sleep(30 * time.Second)
}
}
func dialCoordinator() error {
devMode := !strings.HasPrefix(*coordinator, "farmer.golang.org")
modes := strings.Split(*reverse, ",")
var keys []string
for _, m := range modes {
if devMode {
keys = append(keys, string(devBuilderKey(m)))
continue
}
keyPath := filepath.Join(homedir(), ".gobuildkey-"+m)
key, err := ioutil.ReadFile(keyPath)
if os.IsNotExist(err) && len(modes) == 1 {
globalKeyPath := filepath.Join(homedir(), ".gobuildkey")
key, err = ioutil.ReadFile(globalKeyPath)
if err != nil {
log.Fatalf("cannot read either key file %q or %q: %v", keyPath, globalKeyPath, err)
}
} else if err != nil {
log.Fatalf("cannot read key file %q: %v", keyPath, err)
}
keys = append(keys, string(key))
}
caCert := coordinatorCA
addr := *coordinator
if addr == "farmer.golang.org" {
addr = "farmer.golang.org:443"
}
if devMode {
caCert = testCoordinatorCA
}
caPool := x509.NewCertPool()
if !caPool.AppendCertsFromPEM([]byte(caCert)) {
log.Fatal("failed to append coordinator CA certificate")
}
log.Printf("Dialing coordinator %s...", addr)
tcpConn, err := net.Dial("tcp", addr)
if err != nil {
return err // try again
}
config := &tls.Config{
ServerName: "go",
RootCAs: caPool,
}
conn := tls.Client(tcpConn, config)
if err := conn.Handshake(); err != nil {
return fmt.Errorf("failed to handshake with coordinator: %v", err)
}
bufr := bufio.NewReader(conn)
req, err := http.NewRequest("GET", "/reverse", nil)
if err != nil {
log.Fatal(err)
}
req.Header["X-Go-Builder-Type"] = modes
req.Header["X-Go-Builder-Key"] = keys
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 != 200 {
msg, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("coordinator registration failed:\n\t%s", msg)
}
resp.Body.Close()
// The client becomes the simple http server.
log.Printf("Connected to coordinator, serving HTTP back at them.")
stateCh := make(chan http.ConnState, 1)
srv := &http.Server{
ConnState: func(_ net.Conn, state http.ConnState) { stateCh <- state },
}
return srv.Serve(&reverseListener{
conn: conn,
stateCh: stateCh,
})
}
// reverseListener serves out a single underlying conn, once.
//
// It is designed to be passed to a *http.Server, which loops
// continually calling Accept. As this reverse connection only
// ever has one connection to hand out, it responds to the first
// Accept, and then blocks on the second Accept.
//
// While blocking on the second Accept, this listener takes on the
// job of checking the health of the original net.Conn it handed out.
// If it goes unused for a while, it closes the original net.Conn
// and returns an error, ending the life of the *http.Server
type reverseListener struct {
done bool
conn net.Conn
stateCh <-chan http.ConnState
}
func (rl *reverseListener) Accept() (net.Conn, error) {
if !rl.done {
// First call to Accept, return our one net.Conn.
rl.done = true
return rl.conn, nil
}
// Second call to Accept, block until we decide the entire
// server should be torn down.
defer rl.conn.Close()
const timeout = 1 * time.Minute
timer := time.NewTimer(timeout)
var state http.ConnState
for {
select {
case state = <-rl.stateCh:
if state == http.StateClosed {
return nil, errors.New("coordinator connection closed")
}
// The coordinator sends a health check every 30 seconds
// when buildlets are idle. If we go a minute without
// seeing anything, assume the coordinator is in a bad way
// (probably restarted) and close the connection.
case <-timer.C:
if state == http.StateIdle {
return nil, errors.New("coordinator connection unhealthy")
}
}
timer.Reset(timeout)
}
}
func (rl *reverseListener) Close() error { return nil }
func (rl *reverseListener) Addr() net.Addr { return reverseAddr("buildlet") }
// reverseAddr implements net.Addr for reverseListener.
type reverseAddr string
func (a reverseAddr) Network() string { return "reverse" }
func (a reverseAddr) String() string { return "reverse:" + string(a) }
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")
}
return os.Getenv("HOME")
}
// 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()
}
/*
Certificate authority and the coordinator SSL key were created with:
openssl genrsa -out ca_key.pem 2048
openssl req -x509 -new -key ca_key.pem -out ca_cert.pem -days 1068 -subj /CN="go"
openssl genrsa -out key.pem 2048
openssl req -new -out cert_req.pem -key key.pem -subj /CN="go"
openssl x509 -req -in cert_req.pem -out cert.pem -CAkey ca_key.pem -CA ca_cert.pem -days 730 -CAcreateserial -CAserial serial
*/
// coordinatorCA is the production CA cert for farmer.golang.org.
const coordinatorCA = `-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIJANl4KOv9Cj4UMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
BAMTAmdvMB4XDTE1MDQwNTIwMTE0OFoXDTE4MDMwODIwMTE0OFowDTELMAkGA1UE
AxMCZ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ/oLb+ksvNScl
zIweMGv2ZWRdWW3o9vWIMpOhkiYuBOZjp7zvs89OuKNdC1ylJs3ENnNtD8QOG1Ze
kM3s6MTjCLVZUX4218HAenGifaunTNfbW1/q/tTnZh4Kri00vgq9jFtYnlqFLYhT
PlmDMdpgOY4ligc/1bSPWVsI7CKCbh3fAz67m++opVE0M7LFp8bhkyFv/dnhZFxo
s9ei3ZKFLjYJdZUNRMZ+HcqBzXMQR7HeCOD2pZ1yoHJw1b3Ebe4YOcQCHq4moW7W
DavISKSXl7DKZYX1QlFUmEMkl5aMIEHUJ0oI2wnL9+u5s1NU2/k8sSxbH7Y/cKio
cFPwuMt7AgMBAAGjbjBsMB0GA1UdDgQWBBS5f/j+8YL9B8THnoAXIhQty3vDZjA9
BgNVHSMENjA0gBS5f/j+8YL9B8THnoAXIhQty3vDZqERpA8wDTELMAkGA1UEAxMC
Z2+CCQDZeCjr/Qo+FDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBU
EOOl2ChJyxFg8b4OrG/EC0HMxic2CakRsj6GWQlAwNU8+3o2u2+zYqKhuREDazsZ
1+0f54iU4TXPgPLiOVLQT8AOM6BDDeZfugAopAf0QaIXW5AmM5hnkhW035aXZgx9
rYageMGnnkK2H7E7WlcFbGcPjZtbpZyFnGoAvxcUfOzdnm/LLuvFg6YWf1ynXsNI
aOx5LNVDhzcQlHZ26ueOLoyIpTQxqvo+hwmIOVDLlZ9bz2BS6FevFjsciJmcDL8N
cmY1/5cC/4NzpnN95cvZxp3FX8Ka7YFun03ubjXzXttoeyrxP2WFXuc2D2hkTJPE
Co9z2+Nue1JHG9JcDaeW
-----END CERTIFICATE-----`
// testCoordinatorCA is a dev mode cert, not used in production.
const testCoordinatorCA = `-----BEGIN CERTIFICATE-----
MIIDCzCCAfOgAwIBAgIJAPvaWgVSI9PaMA0GCSqGSIb3DQEBBQUAMA0xCzAJBgNV
BAMTAmdvMB4XDTE1MDQwNjAzMTc0MVoXDTE4MDMwOTAzMTc0MVowDTELMAkGA1UE
AxMCZ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJ6t6PGkTk5CnR
+ZVkHq8w9VgDutnTIED3fWQLZLlc7oyexY4wLqmB/fYxINtmtWg7tUon8Y6SMPBF
51bam7qc69iWYuSUVkhHcQSGYM/OUKXmtl5V2W9HqfHT+Kcqi8Vm2E946LPMCtKJ
JUuzSYYLkXFl8JZw0bi8CROZ23LY7FTZTK/lGUun65bDCTB9AuB/BlclBBtT7pDg
6hSc73tMDWRZZ2c4rY0LXYgqbW9Zs0E8ePrKjHGFKxwQlDu0EKhjN/v6HWwq4qXD
Zlcx8tiPdFIpUOPN5SkpJq80XiDLy1Cqxxc0gdbM1uxIxYwNzlJqwybVqx8E9H/E
y4NAdg0xAgMBAAGjbjBsMB0GA1UdDgQWBBSXjKSDNj0jnlgUsb7lQU6K7CvUGjA9
BgNVHSMENjA0gBSXjKSDNj0jnlgUsb7lQU6K7CvUGqERpA8wDTELMAkGA1UEAxMC
Z2+CCQD72loFUiPT2jAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCl
YGLMKAAXgqr4Wj3sCOHfzeZR7fD0ngJ45eP08woXyc6Lg+2kcaOjNVIQ7k91XacP
XeoWexeVnaNNxc0B3uWGqy54AF+6ZuJ8Ybtm3KiFrjYd4iuvQUS4wYYh8Iu83chX
TjB7sEliFX8+KNSWONw3vULfggMugyTnRilW8qOWd0Xx729NlsvC+OFJc2RVkGoq
bmE4LZKjOf0SAh32d1Ye4hH1lPjWkGnVtXiBZbtqk9Ctc1bn6Vq2UxsE/BbZHBlc
0iKSFmwBiTqOyCs9q9Hpb012HqZYV+4CMBDsR21yAtecSuY8Rse9Vc+POuyRuY25
oObGb36g+BHVuGJxjbFo
-----END CERTIFICATE-----`