21

I have put up TLS and it works. I know how to rewrite from http to https in nginx, but I do not use nginx anymore. I don't know how to do this in Go properly.

func main() {

    certificate := "/srv/ssl/ssl-bundle.crt"
    privateKey := "/srv/ssl/mykey.key"

    http.HandleFunc("/", rootHander)
    // log.Fatal(http.ListenAndServe(":80", nil))
    log.Fatal(http.ListenAndServeTLS(":443", certificate, privateKey, nil))
}

func rootHander(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("To the moon!"))
}

How would I do this in a good way?

Alex
  • 5,671
  • 9
  • 41
  • 81

4 Answers4

28

Create a handler which handles redirection to https like:

func redirectToTls(w http.ResponseWriter, r *http.Request) {
    http.Redirect(w, r, "https://IPAddr:443"+r.RequestURI, http.StatusMovedPermanently)
}

Then redirect http traffic:

go func() {
    if err := http.ListenAndServe(":80", http.HandlerFunc(redirectToTls)); err != nil {
        log.Fatalf("ListenAndServe error: %v", err)
    }
}()
khrm
  • 5,363
  • 1
  • 22
  • 26
  • Thank you so much! – Alex May 31 '16 at 21:52
  • 8
    for the address to redirect to, you'd be better off using `"https://" + r.Host + r.RequestURI`, which will avoid having your hostname or IP address hardcoded. – Benny Jobigan Sep 10 '17 at 08:52
  • `r.Host` is obtained from `Host` header in the http request. I think it should not be trusted (see [this](https://security.stackexchange.com/a/111903) post) and `r.RequestURI` could contains full origin like `http://localhost:5000` (see [this](https://stackoverflow.com/a/6911635/4019871) post) so it's too should not be trusted. – Mas Bagol Jan 01 '20 at 11:00
  • TL;DR, using r.Host is the correct way to do this, despite user fiddling potential. --- Interesting discussion. @MasBagol is quite correct, the user can 'fool' or override r.Host . But, the alternatives are NOT pretty, it could mean asking your admin/user to configure 'IPAddr', which is a BAD idea. Without lots of brittle assumptions about NAT/requestor-environment is it difficult to figure out what to pass to http.Redirect() without using r.Host. And! given the example, the 'hacker' is only managing to get your Go-app to pass a HTTP 301 Moved Permanently back to their own browser. – Cameron Apr 03 '21 at 23:32
  • `redirectTLS` sounds a misleading function name, I would rather use `redirectToTls` – Attila123 May 27 '22 at 13:19
8

The solutions posted above are a little inflexible, especially if the external hostname is different from the local host.

Here is the code I use for HTTP->HTTPS redirects:

package main

import (
    "net"
    "log"
    "net/http"
)

var httpAddr ":8080"
var httpsAddr ":8443"

func main() {
    srv := http.Server{
        Addr: httpsAddr,
    }

    _, tlsPort, err := net.SplitHostPort(httpsAddr)
    if err != nil {
        return err
    }
    go redirectToHTTPS(tlsPort)

    srv.ListenAndServeTLS("cert.pem", "key.pem")
}

func redirectToHTTPS(tlsPort string) {
    httpSrv := http.Server{
        Addr: httpAddr,
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
            host, _, _ := net.SplitHostPort(r.Host)
            u := r.URL
            u.Host = net.JoinHostPort(host, tlsPort)
            u.Scheme="https"
            log.Println(u.String())
            http.Redirect(w,r,u.String(), http.StatusMovedPermanently)
        }),
    }
    log.Println(httpSrv.ListenAndServe())
}

If you are using standard ports (80,443) the splitting of joining of the adresses is not rquired and just setting the scheme on the URL is sufficient.

jabbrwcky
  • 598
  • 5
  • 6
4
package main

import (
    "fmt"
    "net/http"
)

func redirectToHttps(w http.ResponseWriter, r *http.Request) {
    // Redirect the incoming HTTP request. Note that "127.0.0.1:443" will only work if you are accessing the server from your local machine.
    http.Redirect(w, r, "https://127.0.0.1:443"+r.RequestURI, http.StatusMovedPermanently)
}

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hi there!")
    fmt.Println(r.RequestURI)
}

func main() {
    http.HandleFunc("/", handler)
    // Start the HTTPS server in a goroutine
    go http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)
    // Start the HTTP server and redirect all incoming connections to HTTPS
    http.ListenAndServe(":8080", http.HandlerFunc(redirectToHttps))
}
Henry Woody
  • 14,024
  • 7
  • 39
  • 56
abdel
  • 665
  • 1
  • 11
  • 25
  • Thank you for the help! I gave the answer to the other post that was some hours earlier. Have a good day! – Alex May 31 '16 at 21:54
  • Is there a problem here with the explicit 127.0.0.1 address? It might need the domain name contatenated, e.g "https://" + domain + r.RequestURI. – Rick-777 Jun 10 '16 at 14:00
  • Also, 443 is the default port for https and can be omitted. – Rick-777 Jun 10 '16 at 14:01
1

There is another great example and discussion here if you are using your own mux: https://gist.github.com/d-schmidt/587ceec34ce1334a5e60