cmd/buildlet: add localhost proxy to farmer.golang.org's module proxy

Now that farmer.golang.org is an authenticated GOPROXY server (CL 165779),
this adds the unauthenticated localhost server on the buildlet that
adds the auth headers to farmer.golang.org.

The buildlet only does this if the build includes env
GO_BUILDER_SET_GOPROXY=coordinator, in which case it listens on an
emphemeral localhost port per exec and sets GOPROXY to an HTTP server
running on that port. This way we don't need to deal with any port
management or conflicts.

Updates golang/go#14594

Change-Id: Iae6d2deda9d5e88ab659d94aaccc43e01fcf4a7c
Reviewed-on: https://go-review.googlesource.com/c/build/+/165782
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/buildlet/buildlet.go b/cmd/buildlet/buildlet.go
index b999dda..0f0d718 100644
--- a/cmd/buildlet/buildlet.go
+++ b/cmd/buildlet/buildlet.go
@@ -73,7 +73,8 @@
 //   16: make macstadium builders always haltEntireOS
 //   17: make macstadium halts use sudo
 //   18: set TMPDIR and GOCACHE
-const buildletVersion = 18
+//   21: GO_BUILDER_SET_GOPROXY=coordinator support
+const buildletVersion = 21
 
 func defaultListenAddr() string {
 	if runtime.GOOS == "darwin" {
@@ -926,6 +927,18 @@
 
 	env := append(baseEnv(goarch), postEnv...)
 
+	// Setup an localhost HTTP server to proxy module cache, if requested by environment.
+	if goproxyHandler != nil && getEnv(postEnv, "GO_BUILDER_SET_GOPROXY") == "coordinator" {
+		ln, err := net.Listen("tcp", "localhost:0")
+		if err != nil {
+			http.Error(w, "failed to listen on localhost for GOPROXY=coordinator: "+err.Error(), http.StatusInternalServerError)
+			return
+		}
+		defer ln.Close()
+		srv := &http.Server{Handler: goproxyHandler}
+		go srv.Serve(ln)
+		env = append(env, fmt.Sprintf("GOPROXY=http://localhost:%d", ln.Addr().(*net.TCPAddr).Port))
+	}
 	if v := processTmpDirEnv; v != "" {
 		env = append(env, "TMPDIR="+v)
 	}
diff --git a/cmd/buildlet/reverse.go b/cmd/buildlet/reverse.go
index 7d1bee6..b83c1d7 100644
--- a/cmd/buildlet/reverse.go
+++ b/cmd/buildlet/reverse.go
@@ -17,6 +17,7 @@
 	"log"
 	"net"
 	"net/http"
+	"net/http/httputil"
 	"net/url"
 	"os"
 	"path/filepath"
@@ -69,6 +70,34 @@
 	return !strings.HasPrefix(*coordinator, "farmer.golang.org")
 }
 
+// proxyToCoordinatorHandler is a GOPROXY proxy, proxying to
+// https://farmer.golang.org while adding HTTP basic auth (of the
+// reverse buildlet type & its key) and the
+// X-Proxy-Service:module-cache header.
+type proxyToCoordinatorHandler struct {
+	user, pass string
+	rp         *httputil.ReverseProxy
+}
+
+func (h *proxyToCoordinatorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	outReq := r.WithContext(r.Context())
+	outReq.SetBasicAuth(h.user, h.pass)
+	outReq.Header.Set("X-Proxy-Service", "module-cache")
+	h.rp.ServeHTTP(w, outReq)
+}
+
+// goproxyHandler is non-nil for reverse buildlets.
+var goproxyHandler *proxyToCoordinatorHandler
+
+func newProxyToCoordinatorHandler(user, pass string) *proxyToCoordinatorHandler {
+	u, _ := url.Parse("https://farmer.golang.org")
+	return &proxyToCoordinatorHandler{
+		user: user,
+		pass: pass,
+		rp:   httputil.NewSingleHostReverseProxy(u),
+	}
+}
+
 func dialCoordinator() error {
 	devMode := isDevReverseMode()
 
@@ -87,6 +116,7 @@
 			}
 			keys = append(keys, key)
 		}
+		goproxyHandler = newProxyToCoordinatorHandler(modes[0], keys[0])
 	} else {
 		// New way.
 		key, err := keyForMode(*reverseType)
@@ -94,6 +124,7 @@
 			log.Fatalf("failed to find key for %s: %v", *reverseType, err)
 		}
 		keys = append(keys, key)
+		goproxyHandler = newProxyToCoordinatorHandler(*reverseType, key)
 	}
 
 	caCert := build.ProdCoordinatorCA