cmd/tip: redirect http://tip.golang.org to https
At some point we switched tip.golang.org to run in GKE, which
terminates TLS directly on port 443. This requires a new technique
for detecting a plain HTTP connection. In addition we may want to run
talks.golang.org on App Engine Flex, which uses an X-Forwarded-Proto
header to indicate HTTP, so let's prepare for that possibility.
Fixes golang/go#19759.
Change-Id: Iddc567214c5d28f61c405db065aa1b3f2c92fd85
Reviewed-on: https://go-review.googlesource.com/38800
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/cmd/tip/README b/cmd/tip/README
index 9b7d966..b96c107 100644
--- a/cmd/tip/README
+++ b/cmd/tip/README
@@ -30,4 +30,3 @@
TODO(bradfitz): flesh out these instructions as I gain experience
with updating this over time. Also: move talks.golang.org to GKE too?
-
diff --git a/cmd/tip/tip.go b/cmd/tip/tip.go
index 81d1054..cb1bb09 100644
--- a/cmd/tip/tip.go
+++ b/cmd/tip/tip.go
@@ -56,15 +56,14 @@
p := &Proxy{builder: b}
go p.run()
- http.Handle("/", httpsOnlyHandler{p})
- http.HandleFunc("/_ah/health", p.serveHealthCheck)
+ mux := newServeMux(p)
log.Printf("Starting up tip server for builder %q", os.Getenv(k))
errc := make(chan error)
go func() {
- errc <- http.ListenAndServe(":8080", nil)
+ errc <- http.ListenAndServe(":8080", mux)
}()
if *autoCertDomain != "" {
log.Printf("Listening on port 443 with LetsEncrypt support on domain %q", *autoCertDomain)
@@ -74,6 +73,7 @@
}
s := &http.Server{
Addr: ":https",
+ Handler: mux,
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
}
go func() {
@@ -245,6 +245,13 @@
p.cmd = cmd
}
+func newServeMux(p *Proxy) http.Handler {
+ mux := http.NewServeMux()
+ mux.Handle("/", httpsOnlyHandler{p})
+ mux.HandleFunc("/_ah/health", p.serveHealthCheck)
+ return mux
+}
+
func waitReady(b Builder, hostport string) error {
var err error
deadline := time.Now().Add(startTimeout)
@@ -360,20 +367,36 @@
return body, nil
}
-// httpsOnlyHandler redirects requests to "http://example.com/foo?bar"
-// to "https://example.com/foo?bar"
+// httpsOnlyHandler redirects requests to "http://example.com/foo?bar" to
+// "https://example.com/foo?bar". It should be used when the server is listening
+// for HTTP traffic behind a proxy that terminates TLS traffic, not when the Go
+// server is terminating TLS directly.
type httpsOnlyHandler struct {
h http.Handler
}
+// isProxiedReq checks whether the server is running behind a proxy that may be
+// terminating TLS.
+func isProxiedReq(r *http.Request) bool {
+ if _, ok := r.Header["X-Appengine-Https"]; ok {
+ return true
+ }
+ if _, ok := r.Header["X-Forwarded-Proto"]; ok {
+ return true
+ }
+ return false
+}
+
func (h httpsOnlyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- if r.Header.Get("X-Appengine-Https") == "off" {
+ if r.Header.Get("X-Appengine-Https") == "off" || r.Header.Get("X-Forwarded-Proto") == "http" ||
+ (!isProxiedReq(r) && r.TLS == nil) {
r.URL.Scheme = "https"
r.URL.Host = r.Host
http.Redirect(w, r, r.URL.String(), http.StatusFound)
return
}
- if r.Header.Get("X-Appengine-Https") == "on" {
+ if r.Header.Get("X-Appengine-Https") == "on" || r.Header.Get("X-Forwarded-Proto") == "https" ||
+ (!isProxiedReq(r) && r.TLS != nil) {
// Only set this header when we're actually in production.
w.Header().Set("Strict-Transport-Security", "max-age=31536000; preload")
}
diff --git a/cmd/tip/tip_test.go b/cmd/tip/tip_test.go
new file mode 100644
index 0000000..878954d
--- /dev/null
+++ b/cmd/tip/tip_test.go
@@ -0,0 +1,25 @@
+// Copyright 2017 The Go Authors. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+ "net/http/httptest"
+ "testing"
+)
+
+func TestTipRedirects(t *testing.T) {
+ mux := newServeMux(&Proxy{builder: &godocBuilder{}})
+ req := httptest.NewRequest("GET", "http://example.com/foo?bar=baz", nil)
+ req.Header.Set("X-Forwarded-Proto", "http")
+ w := httptest.NewRecorder()
+ mux.ServeHTTP(w, req)
+ if w.Code != 302 {
+ t.Errorf("expected Code to be 302, got %d", w.Code)
+ }
+ want := "https://example.com/foo?bar=baz"
+ if loc := w.Header().Get("Location"); loc != want {
+ t.Errorf("Location header: got %s, want %s", loc, want)
+ }
+}