diff --git a/app.go b/app.go index 79b7145..1fa6f8a 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 ( @@ -380,19 +382,55 @@ 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) { + if app.cfg.Server.Autocert { + m := &autocert.Manager{ + 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", + Handler: r, + TLSConfig: &tls.Config{ + GetCertificate: m.GetCertificate, + }, + } + 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.Error("Unable to start redirect server: %v", err) + }() - 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) + 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) + } } 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"` 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 - - 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 + data.Config.Server.Autocert = sel == 2 + + 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 + } + } else { + // Automatic certificate + data.Config.Server.TLSCertPath = "certs" + data.Config.Server.TLSKeyPath = "certs" } } } else {