From 36fb7ecb2b6f54894dfbd65b2ce8bc9d3949d440 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 20 Jul 2019 20:49:20 -0400 Subject: [PATCH 1/4] Support automatically generated certificates This adds a new config option in the `[server]` section: `autocert`. When true, WF will automatically generate certificates instead of using ones from the provided cert path. However, all generated certificates will be stored in the configured `tls_cert_path`. Ref T542 --- app.go | 28 +++++++++++++++++++++++++--- config/config.go | 1 + 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/app.go b/app.go index 79b7145..00e7d6d 100644 --- a/app.go +++ b/app.go @@ -11,6 +11,7 @@ package writefreely import ( + "crypto/tls" "database/sql" "fmt" "html/template" @@ -39,6 +40,7 @@ import ( "github.com/writeas/writefreely/key" "github.com/writeas/writefreely/migrations" "github.com/writeas/writefreely/page" + "golang.org/x/crypto/acme/autocert" ) const ( @@ -390,9 +392,29 @@ func Serve(app *App, r *mux.Router) { }() log.Info("Serving on https://%s:443", bindAddress) - log.Info("---") - err = http.ListenAndServeTLS( - fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) + if app.cfg.Server.Autocert { + log.Info("Using autocert") + m := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache(app.cfg.Server.TLSCertPath), + HostPolicy: autocert.HostWhitelist(app.cfg.App.Host), + } + s := &http.Server{ + Addr: ":https", + Handler: r, + TLSConfig: &tls.Config{ + GetCertificate: m.GetCertificate, + }, + } + s.SetKeepAlivesEnabled(false) + + log.Info("---") + err = s.ListenAndServeTLS("", "") + } else { + log.Info("Using manual certificates") + log.Info("---") + err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) + } } else { log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port) log.Info("---") diff --git a/config/config.go b/config/config.go index 8009208..58486b0 100644 --- a/config/config.go +++ b/config/config.go @@ -35,6 +35,7 @@ type ( TLSCertPath string `ini:"tls_cert_path"` TLSKeyPath string `ini:"tls_key_path"` + Autocert bool `ini:"autocert"` TemplatesParentDir string `ini:"templates_parent_dir"` StaticParentDir string `ini:"static_parent_dir"` From 42386beabca2be159d542521749a9399ae2f6290 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 20 Jul 2019 21:34:58 -0400 Subject: [PATCH 2/4] Fix autocert HostPolicy Previously, this would pass in the instance's full (and invalid) URL. Now it passes only the host name. Ref T542 --- app.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index 00e7d6d..c9a770e 100644 --- a/app.go +++ b/app.go @@ -393,11 +393,21 @@ func Serve(app *App, r *mux.Router) { log.Info("Serving on https://%s:443", bindAddress) if app.cfg.Server.Autocert { - log.Info("Using autocert") m := &autocert.Manager{ - Prompt: autocert.AcceptTOS, - Cache: autocert.DirCache(app.cfg.Server.TLSCertPath), - HostPolicy: autocert.HostWhitelist(app.cfg.App.Host), + Prompt: autocert.AcceptTOS, + Cache: autocert.DirCache(app.cfg.Server.TLSCertPath), + } + host, err := url.Parse(app.cfg.App.Host) + if err != nil { + log.Error("[WARNING] Unable to parse configured host! %s", err) + log.Error(`[WARNING] ALL hosts are allowed, which can open you to an attack where +clients connect to a server by IP address and pretend to be asking for an +incorrect host name, and cause you to reach the CA's rate limit for certificate +requests. We recommend supplying a valid host name.`) + log.Info("Using autocert on ANY host") + } else { + log.Info("Using autocert on host %s", host.Host) + m.HostPolicy = autocert.HostWhitelist(host.Host) } s := &http.Server{ Addr: ":https", From 3346e735d389c5cfb674082a2441090bb99562c6 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 20 Jul 2019 21:38:02 -0400 Subject: [PATCH 3/4] Fix autocert insecure server redirect This fixes certificate validation, while keeping HTTP -> HTTPS redirection. Ref T542 --- app.go | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/app.go b/app.go index c9a770e..1fa6f8a 100644 --- a/app.go +++ b/app.go @@ -382,16 +382,6 @@ func Serve(app *App, r *mux.Router) { } var err error if app.cfg.IsSecureStandalone() { - log.Info("Serving redirects on http://%s:80", bindAddress) - go func() { - err = http.ListenAndServe( - fmt.Sprintf("%s:80", bindAddress), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, app.cfg.App.Host, http.StatusMovedPermanently) - })) - log.Error("Unable to start redirect server: %v", err) - }() - - log.Info("Serving on https://%s:443", bindAddress) if app.cfg.Server.Autocert { m := &autocert.Manager{ Prompt: autocert.AcceptTOS, @@ -418,9 +408,25 @@ requests. We recommend supplying a valid host name.`) } s.SetKeepAlivesEnabled(false) + go func() { + log.Info("Serving redirects on http://%s:80", bindAddress) + err = http.ListenAndServe(":80", m.HTTPHandler(nil)) + log.Error("Unable to start redirect server: %v", err) + }() + + log.Info("Serving on https://%s:443", bindAddress) log.Info("---") err = s.ListenAndServeTLS("", "") } else { + go func() { + log.Info("Serving redirects on http://%s:80", bindAddress) + err = http.ListenAndServe(fmt.Sprintf("%s:80", bindAddress), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, app.cfg.App.Host, http.StatusMovedPermanently) + })) + log.Error("Unable to start redirect server: %v", err) + }() + + log.Info("Serving on https://%s:443", bindAddress) log.Info("Using manual certificates") log.Info("---") err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) From 1f7a0f0122f30e697d188baab832b9c8e049e04b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 20 Jul 2019 21:46:10 -0400 Subject: [PATCH 4/4] Add option for automated cert in config process This adds a new "Secure (port 443), auto certificate" option to the "Web server mode" prompt when running `writefreely --config`. When chosen, it'll set `autocert` to `true` and set the path for certs and keys to `certs`. Ref T542 --- config/setup.go | 51 +++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/config/setup.go b/config/setup.go index b1c0c37..fd5a632 100644 --- a/config/setup.go +++ b/config/setup.go @@ -101,39 +101,48 @@ func Configure(fname string, configSections string) (*SetupData, error) { selPrompt = promptui.Select{ Templates: selTmpls, Label: "Web server mode", - Items: []string{"Insecure (port 80)", "Secure (port 443)"}, + Items: []string{"Insecure (port 80)", "Secure (port 443), manual certificate", "Secure (port 443), auto certificate"}, } sel, _, err := selPrompt.Run() if err != nil { return data, err } if sel == 0 { + data.Config.Server.Autocert = false data.Config.Server.Port = 80 data.Config.Server.TLSCertPath = "" data.Config.Server.TLSKeyPath = "" - } else if sel == 1 { + } else if sel == 1 || sel == 2 { data.Config.Server.Port = 443 + data.Config.Server.Autocert = sel == 2 - prompt = promptui.Prompt{ - Templates: tmpls, - Label: "Certificate path", - Validate: validateNonEmpty, - Default: data.Config.Server.TLSCertPath, - } - data.Config.Server.TLSCertPath, err = prompt.Run() - if err != nil { - return data, err - } + if sel == 1 { + // Manual certificate configuration + prompt = promptui.Prompt{ + Templates: tmpls, + Label: "Certificate path", + Validate: validateNonEmpty, + Default: data.Config.Server.TLSCertPath, + } + data.Config.Server.TLSCertPath, err = prompt.Run() + if err != nil { + return data, err + } - prompt = promptui.Prompt{ - Templates: tmpls, - Label: "Key path", - Validate: validateNonEmpty, - Default: data.Config.Server.TLSKeyPath, - } - data.Config.Server.TLSKeyPath, err = prompt.Run() - if err != nil { - return data, err + prompt = promptui.Prompt{ + Templates: tmpls, + Label: "Key path", + Validate: validateNonEmpty, + Default: data.Config.Server.TLSKeyPath, + } + data.Config.Server.TLSKeyPath, err = prompt.Run() + if err != nil { + return data, err + } + } else { + // Automatic certificate + data.Config.Server.TLSCertPath = "certs" + data.Config.Server.TLSKeyPath = "certs" } } } else {