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>
This commit is contained in:
Kevin Burke 2017-03-31 16:31:41 -07:00 committed by Brad Fitzpatrick
parent 620ecdb8d7
commit 37a1062ad0
3 changed files with 55 additions and 8 deletions

View File

@ -30,4 +30,3 @@ Kubernetes instructions:
TODO(bradfitz): flesh out these instructions as I gain experience
with updating this over time. Also: move talks.golang.org to GKE too?

View File

@ -56,15 +56,14 @@ func main() {
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 @@ func main() {
}
s := &http.Server{
Addr: ":https",
Handler: mux,
TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
}
go func() {
@ -245,6 +245,13 @@ func (p *Proxy) poll() {
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 @@ func getOK(url string) (body []byte, err error) {
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")
}

25
cmd/tip/tip_test.go Normal file
View File

@ -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)
}
}