-
Notifications
You must be signed in to change notification settings - Fork 104
golink: listen on HTTPS and redirect HTTP traffic #99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
2fc65c3
23f9f96
5722cf2
5e70935
7917da1
8ac87f5
39b1540
7e903c1
9cabf69
a1ea1da
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ import ( | |
"bytes" | ||
"context" | ||
"crypto/rand" | ||
"crypto/tls" | ||
"embed" | ||
"encoding/base64" | ||
"encoding/json" | ||
|
@@ -169,18 +170,78 @@ func Run() error { | |
if err := srv.Start(); err != nil { | ||
return err | ||
} | ||
localClient, _ = srv.LocalClient() | ||
|
||
l80, err := srv.Listen("tcp", ":80") | ||
// create tsNet server and wait for it to be ready & connected. | ||
localClient, _ = srv.LocalClient() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While we're here, maybe now is a good time to localClient, err = srv.LocalClient()
if err != nil {
return err
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as the server is started, |
||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
defer cancel() | ||
_, err = srv.Up(ctx) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
log.Printf("Serving http://%s/ ...", *hostname) | ||
if err := http.Serve(l80, serveHandler()); err != nil { | ||
return err | ||
enableTLS := len(srv.CertDomains()) > 0 | ||
if enableTLS { | ||
// warm the certificate cache for all cert domains to prevent users waiting | ||
// on ACME challenges in-line on their first request. | ||
for _, d := range srv.CertDomains() { | ||
log.Printf("Provisioning TLS certificate for %s ...", d) | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute) | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
defer cancel() | ||
|
||
_, _, err := localClient.CertPair(ctx, d) | ||
if err != nil { | ||
return err | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
redirectFqdn := srv.CertDomains()[0] | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// HTTP listener that redirects to our HTTPS listener. | ||
log.Println("Listening on :80") | ||
httpListener, err := srv.Listen("tcp", ":80") | ||
if err != nil { | ||
return err | ||
} | ||
go func() error { | ||
log.Printf("Serving http://%s/ ...", *hostname) | ||
if err := http.Serve(httpListener, redirectHandler(redirectFqdn)); err != nil { | ||
return err | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
return nil | ||
}() | ||
|
||
log.Println("Listening on :443") | ||
httpsListener, err := srv.Listen("tcp", ":443") | ||
if err != nil { | ||
return err | ||
} | ||
s := http.Server{ | ||
Addr: ":443", | ||
Handler: HSTS(serveHandler()), | ||
TLSConfig: &tls.Config{ | ||
GetCertificate: localClient.GetCertificate, | ||
}, | ||
} | ||
|
||
log.Printf("Serving https://%s/\n", redirectFqdn) | ||
if err := s.ServeTLS(httpsListener, "", ""); err != nil { | ||
return err | ||
} | ||
return nil | ||
} else { | ||
// no TLS, just serve on :80 | ||
log.Println("Listening on :80") | ||
httpListener, err := srv.Listen("tcp", ":80") | ||
if err != nil { | ||
return err | ||
} | ||
log.Printf("Serving http://%s/ ...", *hostname) | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err := http.Serve(httpListener, serveHandler()); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
return nil | ||
|
||
} | ||
|
||
var ( | ||
|
@@ -286,6 +347,25 @@ func deleteLinkStats(link *Link) { | |
db.DeleteStats(link.Short) | ||
} | ||
|
||
// redirectHandler returns the http.Handler for serving all plaintext HTTP | ||
// requests. It redirects all requests to the HTTPs version of the same URL. | ||
func redirectHandler(hostname string) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
path := r.URL.Path | ||
newUrl := fmt.Sprintf("https://%s%s", hostname, path) | ||
patrickod marked this conversation as resolved.
Show resolved
Hide resolved
|
||
http.Redirect(w, r, newUrl, http.StatusMovedPermanently) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not entirely sure this works, I vaguely recall something about |
||
}) | ||
} | ||
|
||
// HSTS wraps the provided handler and sets Strict-Transport-Security header on | ||
// all responses. | ||
func HSTS(h http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Set("Strict-Transport-Security", "max-age=31536000") | ||
h.ServeHTTP(w, r) | ||
}) | ||
} | ||
|
||
// serverHandler returns the main http.Handler for serving all requests. | ||
func serveHandler() http.Handler { | ||
mux := http.NewServeMux() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -114,15 +114,15 @@ <h2 id="api">Application Programming Interface (API)</h2> | |
Visit <a href="/.export">go/.export</a> to export all saved links and their metadata in <a href="https://jsonlines.org/">JSON Lines format</a>. | ||
This is useful to create data snapshots that can be restored later. | ||
|
||
<pre>{{`$ curl go/.export | ||
<pre>{{`$ curl -L go/.export | ||
{"Short":"go","Long":"http://go","Created":"2022-05-31T13:04:44.741457796-07:00","LastEdit":"2022-05-31T13:04:44.741457796-07:00","Owner":"[email protected]","Clicks":1} | ||
{"Short":"slack","Long":"https://company.slack.com/{{if .Path}}channels/{{PathEscape .Path}}{{end}}","Created":"2022-06-17T18:05:43.562948451Z","LastEdit":"2022-06-17T18:06:35.811398Z","Owner":"[email protected]","Clicks":4}`}} | ||
</pre> | ||
|
||
<p> | ||
Create a new link by sending a POST request with a <code>short</code> and <code>long</code> value: | ||
|
||
<pre>{{`$ curl -d short=cs -d long=https://cs.github.com/ go | ||
<pre>{{`$ curl -L -d short=cs -d long=https://cs.github.com/ go | ||
{"Short":"cs","Long":"https://cs.github.com/","Created":"2022-06-03T22:15:29.993978392Z","LastEdit":"2022-06-03T22:15:29.993978392Z","Owner":"[email protected]"}`}} | ||
</pre> | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.