Last active
March 16, 2020 10:59
-
-
Save dmitshur/29ceb007de7fc553227129b523b720f1 to your computer and use it in GitHub Desktop.
Revisions
-
dmitshur revised this gist
Sep 7, 2019 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,7 +6,7 @@ // // • redirects all HTTP requests to HTTPS // // • gates certain endpoints with basic auth, using bcrypt-hashed passwords // package main @@ -128,7 +128,7 @@ func (h customHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { case "private1.example.com", "private2.example.com": _, pw, ok := req.BasicAuth() if !ok { w.Header().Set("Www-Authenticate", "Basic") http.Error(w, "401 Unauthorized", http.StatusUnauthorized) return } -
dmitshur revised this gist
Sep 7, 2019 . 1 changed file with 14 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -109,6 +109,20 @@ type customHandler struct { } func (h customHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Redirect to canonical host, if needed. var canonicalHost string switch req.Host { case "anotherdomain.example", "www.anotherdomain.example", "foobar.anotherdomain.example": canonicalHost = "anotherdomain.example" } if canonicalHost != "" && req.Host != canonicalHost { u := *req.URL u.Scheme = "https" // Needs to be set explicitly because incoming request provides relative path. u.Host = canonicalHost http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect) return } // Basic auth. switch req.Host { case "private1.example.com", "private2.example.com": -
dmitshur created this gist
Sep 7, 2019 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,165 @@ // A simple server for HTTPS and HTTP protocols. It implements these behaviors: // // • uses Let's Encrypt to acquire and automatically refresh HTTPS certificates // // • redirects HTTPS requests to canonical hosts, reverse proxies requests to internal backing servers // // • redirects all HTTP requests to HTTPS // // • gates certain endpoints via bcrypt-hashed basic auth passwords // package main import ( "context" "encoding/json" "flag" "fmt" "log" "net/http" "net/http/httputil" "os" "os/signal" "path/filepath" "time" "golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/bcrypt" ) func main() { flag.Parse() int := make(chan os.Signal, 1) signal.Notify(int, os.Interrupt) ctx, cancel := context.WithCancel(context.Background()) go func() { <-int; cancel() }() err := run(ctx) if err != nil { log.Fatalln(err) } } func run(ctx context.Context) error { cacheDir, err := os.UserCacheDir() if err != nil { return err } m := &autocert.Manager{ Cache: autocert.DirCache(filepath.Join(cacheDir, "golang-autocert")), Prompt: autocert.AcceptTOS, HostPolicy: autocert.HostWhitelist( "example.com", "anotherdomain.example", "www.anotherdomain.example", "foobar.anotherdomain.example", "private1.example.com", "private2.example.com", ), Email: "you@example.com", } var basicAuthHashes map[string][]byte // Host -> Hash. err = jsonDecodeFile(filepath.Join("...", "basicauth.json"), &basicAuthHashes) if err != nil { return err } httpsServer := &http.Server{ Addr: ":https", TLSConfig: m.TLSConfig(), Handler: customHandler{Router: newRouter(), BasicAuthHashes: basicAuthHashes}, } httpServer := &http.Server{ Addr: ":http", Handler: m.HTTPHandler(nil), } errCh := make(chan error) go func() { log.Println("Starting HTTPS server.") err := httpsServer.ListenAndServeTLS("", "") log.Println("Ended HTTPS server.") errCh <- fmt.Errorf("httpsServer.ListenAndServeTLS: %v", err) }() go func() { log.Println("Starting HTTP server.") err := httpServer.ListenAndServe() log.Println("Ended HTTP server.") errCh <- fmt.Errorf("httpServer.ListenAndServe: %v", err) }() select { case <-ctx.Done(): err := httpsServer.Close() if err != nil { log.Println("httpsServer.Close:", err) } err = httpServer.Close() if err != nil { log.Println("httpServer.Close:", err) } return nil case err := <-errCh: return err } } type customHandler struct { Router http.Handler BasicAuthHashes map[string][]byte // Host -> Hash. } func (h customHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { // Basic auth. switch req.Host { case "private1.example.com", "private2.example.com": _, pw, ok := req.BasicAuth() if !ok { w.Header().Set("WWW-Authenticate", "Basic") http.Error(w, "401 Unauthorized", http.StatusUnauthorized) return } hash, ok := h.BasicAuthHashes[req.Host] if !ok { http.Error(w, "403 Forbidden", http.StatusForbidden) return } if err := bcrypt.CompareHashAndPassword(hash, []byte(pw)); err != nil { http.Error(w, "403 Forbidden", http.StatusForbidden) return } } h.Router.ServeHTTP(w, req) } func newRouter() http.Handler { director := func(req *http.Request) { switch req.Host { default: // Primary domain. req.URL.Scheme = "http" req.URL.Host = "127.0.0.1:10000" case "anotherdomain.example", "www.anotherdomain.example", "foobar.anotherdomain.example": req.URL.Scheme = "http" req.URL.Host = "127.0.0.1:10001" case "private1.example.com", "private2.example.com": req.URL.Scheme = "http" req.URL.Host = "127.0.0.1:10002" } // Pass req.Host through unmodified, so the target server has access // to the original req.Host value. } return &httputil.ReverseProxy{ Director: director, FlushInterval: 1 * time.Second, } } // jsonDecodeFile decodes contents of file at path into v. func jsonDecodeFile(path string, v interface{}) error { f, err := os.Open(path) if err != nil { return err } defer f.Close() return json.NewDecoder(f).Decode(v) }