server/redirect.go
package server
import (
"bufio"
"crypto/tls"
"log"
"net"
"net/http"
"net/url"
)
// ListenAndServeWithRedirect enables seamless HTTP -> HTTPS redirect
// on the same port. This is useful for Hawk so that if someone
// accesses the :7630 port over HTTP, it'll automagically redirect to
// HTTPS.
func ListenAndServeWithRedirect(addr string, handler http.Handler, cert string, key string) {
config := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
},
}
if config.NextProtos == nil {
config.NextProtos = []string{"http/1.1"}
}
var err error
config.Certificates = make([]tls.Certificate, 1)
config.Certificates[0], err = tls.LoadX509KeyPair(cert, key)
if err != nil {
log.Fatal(err)
}
ln, err := net.Listen("tcp", addr)
if err != nil {
log.Fatal(err)
}
listener := &splitListener{
Listener: ln,
config: config,
}
srv := &http.Server{
Addr: addr,
Handler: &httpRedirectHandler{
handler: handler,
},
TLSConfig: config,
}
err = srv.Serve(listener)
if err != nil {
log.Fatal(err)
}
}
type splitListener struct {
net.Listener
config *tls.Config
}
func (l *splitListener) Accept() (net.Conn, error) {
c, err := l.Listener.Accept()
if err != nil {
return nil, err
}
bconn := &conn{
Conn: c,
buf: bufio.NewReader(c),
}
// inspect the first bytes to see if it is HTTPS
hdr, err := bconn.buf.Peek(6)
if err != nil {
log.Printf("Short %s: %s\n", c.RemoteAddr().String(), err.Error())
// couldn't peek, assume it's HTTPS
return tls.Server(bconn, l.config), nil
}
// SSL 3.0 or TLS 1.0, 1.1 and 1.2
if hdr[0] == 0x16 && hdr[1] == 0x3 && hdr[5] == 0x1 {
return tls.Server(bconn, l.config), nil
// SSL 2
} else if hdr[0] == 0x80 {
return tls.Server(bconn, l.config), nil
}
return bconn, nil
}
type conn struct {
net.Conn
buf *bufio.Reader
}
func (c *conn) Read(b []byte) (int, error) {
return c.buf.Read(b)
}
type httpRedirectHandler struct {
handler http.Handler
}
func (handler *httpRedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
if r.TLS == nil {
u := url.URL{
Scheme: "https",
Opaque: r.URL.Opaque,
User: r.URL.User,
Host: r.Host,
Path: r.URL.Path,
RawQuery: r.URL.RawQuery,
Fragment: r.URL.Fragment,
}
log.Printf("http -> %s\n", u.String())
http.Redirect(w, r, u.String(), http.StatusMovedPermanently)
return
}
handler.handler.ServeHTTP(w, r)
}