From 83fbef2d2f44653955fda2ca63c418b9cb92b010 Mon Sep 17 00:00:00 2001 From: kataras Date: Sat, 26 Aug 2017 01:28:30 +0300 Subject: [PATCH] Happy weekend! Due the latest news we have a single change for your own safety. `iris.AutoTLS` users should pass all the necessary information now, these are the recommended by letsencrypt. Iris devs should declare all the information now, there is no option to "leave something out" anymore, it's for your own good. Version is not changed yet, giving you time to see that changelog and do the necessary changes to your codebase. Happy weekend! Former-commit-id: 490ce14a1022a2b81d347d7f59c2bb5412cfcdf2 --- README.md | 21 ++----- core/host/proxy.go | 40 ++++++++++++- core/host/supervisor.go | 129 ++++++++++++++++++++++++++++++++-------- iris.go | 31 ++++++++-- 4 files changed, 172 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 37ccf7a0..09099e13 100644 --- a/README.md +++ b/README.md @@ -135,11 +135,13 @@ $ go run main.go
Hello World with Go 1.9 -If you've installed Go 1.9 then you can omit the `github.com/kataras/iris/context` package from the imports statement. +Go 1.9 just released. + +Dcumentation and examples will be updated soon to use the already-type aliases inside the framework, such as `iris.Context` instead of the origin pacage. + +If you've installed [Go 1.9](https://golang.org/dl) then you can omit the `github.com/kataras/iris/context` package from the imports statement. ```go -// +build go1.9 - package main import "github.com/kataras/iris" @@ -157,19 +159,6 @@ func main() { } ``` -We expect Go version 1.9 to be released in August, however you can install Go 1.9 RC2 today. - -### Installing Go 1.9rc2 - -1. Go to https://golang.org/dl/#go1.9rc2 -2. Download a compatible, with your OS, archive or executable, i.e `go1.9rc2.windows-amd64.zip` -3. Unzip the contents of `go1.9rc2.windows-amd64.zip` folder to your $GOROOT, i.e `C:\Go` or just execute the executable you've just download -4. Open a terminal and execute `go version`, it should output the go1.9rc2 version, i.e: -```sh -C:\Users\kataras>go version -go version go1.9rc2 windows/amd64 -``` -
diff --git a/core/host/proxy.go b/core/host/proxy.go index abacfa30..11cbdda4 100644 --- a/core/host/proxy.go +++ b/core/host/proxy.go @@ -6,6 +6,7 @@ import ( "net/http/httputil" "net/url" "strings" + "time" "github.com/kataras/iris/core/netutil" ) @@ -59,13 +60,13 @@ func ProxyHandler(target *url.URL) *httputil.ReverseProxy { } // NewProxy returns a new host (server supervisor) which -// redirects all requests to the target. +// proxies all requests to the target. // It uses the httputil.NewSingleHostReverseProxy. // // Usage: // target, _ := url.Parse("https://mydomain.com") // proxy := NewProxy("mydomain.com:80", target) -// proxy.ListenAndServe() // use of proxy.Shutdown to close the proxy server. +// proxy.ListenAndServe() // use of `proxy.Shutdown` to close the proxy server. func NewProxy(hostAddr string, target *url.URL) *Supervisor { proxyHandler := ProxyHandler(target) proxy := New(&http.Server{ @@ -75,3 +76,38 @@ func NewProxy(hostAddr string, target *url.URL) *Supervisor { return proxy } + +// NewRedirection returns a new host (server supervisor) which +// redirects all requests to the target. +// Usage: +// target, _ := url.Parse("https://mydomain.com") +// r := NewRedirection(":80", target, 307) +// r.ListenAndServe() // use of `r.Shutdown` to close this server. +func NewRedirection(hostAddr string, target *url.URL, redirectStatus int) *Supervisor { + targetURI := target.String() + if redirectStatus <= 300 { + // here we should use StatusPermanentRedirect but + // that may result on unexpected behavior + // for end-developers who might change their minds + // after a while, so keep status temporary. + // Note thatwe could also use StatusFound + // as we do on the `Context#Redirect`. + // It will also help us to prevent any post data issues. + redirectStatus = http.StatusTemporaryRedirect + } + + redirectSrv := &http.Server{ + ReadTimeout: 30 * time.Second, + WriteTimeout: 60 * time.Second, + Addr: hostAddr, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + redirectTo := singleJoiningSlash(targetURI, r.URL.Path) + if len(r.URL.RawQuery) > 0 { + redirectTo += "?" + r.URL.RawQuery + } + http.Redirect(w, r, redirectTo, redirectStatus) + }), + } + + return New(redirectSrv) +} diff --git a/core/host/supervisor.go b/core/host/supervisor.go index 688f2f44..bad6599f 100644 --- a/core/host/supervisor.go +++ b/core/host/supervisor.go @@ -5,12 +5,16 @@ import ( "crypto/tls" "net" "net/http" + "net/url" + "strings" "sync" "sync/atomic" + "time" + + "golang.org/x/crypto/acme/autocert" "github.com/kataras/iris/core/errors" "github.com/kataras/iris/core/netutil" - "golang.org/x/crypto/acme/autocert" ) // Configurator provides an easy way to modify @@ -231,46 +235,119 @@ func (su *Supervisor) ListenAndServe() error { return su.Serve(l) } -func setupHTTP2(cfg *tls.Config) { - cfg.NextProtos = append(cfg.NextProtos, "h2") // HTTP2 -} - // ListenAndServeTLS acts identically to ListenAndServe, except that it // expects HTTPS connections. Additionally, files containing a certificate and // matching private key for the server must be provided. If the certificate // is signed by a certificate authority, the certFile should be the concatenation // of the server's certificate, any intermediates, and the CA's certificate. func (su *Supervisor) ListenAndServeTLS(certFile string, keyFile string) error { - if certFile == "" || keyFile == "" { - return errors.New("certFile or keyFile missing") - } - cfg := new(tls.Config) - var err error - cfg.Certificates = make([]tls.Certificate, 1) - if cfg.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil { - return err + su.manuallyTLS = true + + if certFile != "" && keyFile != "" { + cfg := new(tls.Config) + var err error + cfg.Certificates = make([]tls.Certificate, 1) + if cfg.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile); err != nil { + return err + } + + su.Server.TLSConfig = cfg + return su.ListenAndServe() } - setupHTTP2(cfg) - su.Server.TLSConfig = cfg - su.manuallyTLS = true - return su.ListenAndServe() + if su.Server.TLSConfig == nil { + return errors.New("certFile or keyFile missing") + } + + return su.supervise(func() error { return su.Server.ListenAndServeTLS("", "") }) } // ListenAndServeAutoTLS acts identically to ListenAndServe, except that it -// expects HTTPS connections. server's certificates are auto generated from LETSENCRYPT using +// expects HTTPS connections. Server's certificates are auto generated from LETSENCRYPT using // the golang/x/net/autocert package. -func (su *Supervisor) ListenAndServeAutoTLS() error { - autoTLSManager := autocert.Manager{ - Prompt: autocert.AcceptTOS, +// +// The whitelisted domains are separated by whitespace in "domain" argument, i.e "iris-go.com". +// If empty, all hosts are currently allowed. This is not recommended, +// as it opens a potential attack where clients connect to a server +// by IP address and pretend to be asking for an incorrect host name. +// Manager will attempt to obtain a certificate for that host, incorrectly, +// eventually reaching the CA's rate limit for certificate requests +// and making it impossible to obtain actual certificates. +// +// For an "e-mail" use a non-public one, letsencrypt needs that for your own security. +// +// The "cacheDir" is being, optionally, used to provide cache +// stores and retrieves previously-obtained certificates. +// If empty, certs will only be cached for the lifetime of the auto tls manager. +// +// Note: If domain is not empty and the server's port was "443" then +// it will start a new server, automaticall for you, which will redirect all +// http versions to their https as well. +func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDir string) error { + var ( + cache autocert.Cache + hostPolicy autocert.HostPolicy + ) + + if cacheDir != "" { + cache = autocert.DirCache(cacheDir) + } + + if domain != "" { + domains := strings.Split(domain, " ") + hostPolicy = autocert.HostWhitelist(domains...) + } + + autoTLSManager := &autocert.Manager{ + Prompt: autocert.AcceptTOS, + HostPolicy: hostPolicy, + Email: email, + Cache: cache, + } + + cfg := &tls.Config{ + GetCertificate: autoTLSManager.GetCertificate, + MinVersion: tls.VersionTLS10, + PreferServerCipherSuites: true, + CurvePreferences: []tls.CurveID{ + tls.X25519, + }, } - cfg := new(tls.Config) - cfg.GetCertificate = autoTLSManager.GetCertificate - setupHTTP2(cfg) su.Server.TLSConfig = cfg - su.manuallyTLS = true - return su.ListenAndServe() + + // Redirect all http://$path requests to their + // https://$path versions if a specific domain is passed on + // and the port was 443. + if hostPolicy != nil && netutil.ResolvePort(su.Server.Addr) == 443 { + // find the first domain if more than one. + spaceIdx := strings.IndexByte(domain, ' ') + if spaceIdx != -1 { + domain = domain[0:spaceIdx] + } + // create the url for the secured server. + target, err := url.Parse("https://" + domain) + if err != nil { + return err + } + + // create the redirect server. + redirectSrv := NewRedirection(":80", target, -1) + // register a shutdown callback to this + // supervisor in order to close the "secondary redirect server" as well. + + su.RegisterOnShutdown(func() { + // give it some time to close itself... + timeout := 5 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + redirectSrv.Shutdown(ctx) + }) + // start that redirect server using a different goroutine. + go redirectSrv.ListenAndServe() + } + + return su.ListenAndServeTLS("", "") } // RegisterOnShutdown registers a function to call on Shutdown. diff --git a/iris.go b/iris.go index bfe0e360..d9870401 100644 --- a/iris.go +++ b/iris.go @@ -552,9 +552,24 @@ func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator // certifications created on the fly by the "autocert" golang/x package, // so localhost may not be working, use it at "production" machine. // -// Addr should have the form of [host]:port, i.e mydomain.com:443. +// Addr should have the form of [host]:port, i.e mydomain.com:443 or :443. // -// Second argument is optional, it accepts one or more +// The whitelisted domains are separated by whitespace in "domain" argument, +// i.e "iris-go.com", can be different than "addr". +// If empty, all hosts are currently allowed. This is not recommended, +// as it opens a potential attack where clients connect to a server +// by IP address and pretend to be asking for an incorrect host name. +// Manager will attempt to obtain a certificate for that host, incorrectly, +// eventually reaching the CA's rate limit for certificate requests +// and making it impossible to obtain actual certificates. +// +// For an "e-mail" use a non-public one, letsencrypt needs that for your own security. +// +// Note: If domain is not empty and the server's port was "443" then +// it will start a new server, automaticall for you, which will redirect all +// http versions to their https as well. +// +// Last argument is optional, it accepts one or more // `func(*host.Configurator)` that are being executed // on that specific host that this function will create to start the server. // Via host configurators you can configure the back-end host supervisor, @@ -563,12 +578,18 @@ func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator // https://github.com/kataras/iris/blob/master/_examples/http-listening/notify-on-shutdown/main.go // Look at the `ConfigureHost` too. // -// See `Run` for more. -func AutoTLS(addr string, hostConfigs ...host.Configurator) Runner { +// Usage: +// app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com")) +// +// See `Run` and `core/host/Supervisor#ListenAndServeAutoTLS` for more. +func AutoTLS( + addr string, + domain string, email string, + hostConfigs ...host.Configurator) Runner { return func(app *Application) error { return app.NewHost(&http.Server{Addr: addr}). Configure(hostConfigs...). - ListenAndServeAutoTLS() + ListenAndServeAutoTLS(domain, email, "letscache") } }