diff --git a/HISTORY.md b/HISTORY.md index e531574e..2e69656a 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -371,6 +371,9 @@ Other Improvements: ![DBUG routes](https://iris-go.com/images/v12.2.0-dbug2.png?v=0) +- `iris.TLS` can now accept certificates as raw contents too. +- `iris.TLS` registers a secondary http server which redirects "http://" to their "https://" equivalent requests, unless the new `iris.TLSNoRedirect` host Configurator is provided on `iris.TLS` (or `iris.AutoTLS`), e.g. `app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key", iris.TLSNoRedirect))`. + - Fix an [issue](https://github.com/kataras/i18n/issues/1) about i18n loading from path which contains potential language code. - Server will not return neither log the `ErrServerClosed` if `app.Shutdown` was called manually via interrupt signal(CTRL/CMD+C), note that if the server closed by any other reason the error will be fired as previously (unless `iris.WithoutServerError(iris.ErrServerClosed)`). diff --git a/_examples/http-listening/listen-letsencrypt/main.go b/_examples/http-listening/listen-letsencrypt/main.go index 1af9c3fd..16505d39 100644 --- a/_examples/http-listening/listen-letsencrypt/main.go +++ b/_examples/http-listening/listen-letsencrypt/main.go @@ -24,4 +24,9 @@ func main() { // use real whitelisted domain(or domains split by whitespaces) // and a non-public e-mail instead or edit your hosts file. app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com")) + + // Note: to disable automatic "http://" to "https://" redirections pass the `iris.TLSNoRedirect` + // host configurator to TLS or AutoTLS functions, e.g: + // + // app.Run(iris.AutoTLS(":443", "example.com", "mail@example.com", iris.TLSNoRedirect)) } diff --git a/_examples/http-listening/listen-tls/main.go b/_examples/http-listening/listen-tls/main.go index 35cf0e92..a9dee519 100644 --- a/_examples/http-listening/listen-tls/main.go +++ b/_examples/http-listening/listen-tls/main.go @@ -1,11 +1,7 @@ package main import ( - "net/url" - "github.com/kataras/iris/v12" - - "github.com/kataras/iris/v12/core/host" ) func main() { @@ -19,11 +15,13 @@ func main() { ctx.Writef("Hello from the SECURE server on path /mypath") }) - // to start a new server listening at :80 and redirects - // to the secure address, then: - target, _ := url.Parse("https://127.0.0.1:443") - go host.NewRedirection("127.0.0.1:80", target, iris.StatusMovedPermanently).ListenAndServe() - - // start the server (HTTPS) on port 443, this is a blocking func + // Start the server (HTTPS) on port 443, + // and a secondary of (HTTP) on port :80 which redirects requests to their HTTPS version. + // This is a blocking func. app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key")) + + // Note: to disable automatic "http://" to "https://" redirections pass the `iris.TLSNoRedirect` + // host configurator to TLS or AutoTLS functions, e.g: + // + // app.Run(iris.TLS("127.0.0.1:443", "mycert.cert", "mykey.key", iris.TLSNoRedirect)) } diff --git a/_examples/mvc/grpc-compatible/helloworld/helloworld.proto b/_examples/mvc/grpc-compatible/helloworld/helloworld.proto index 8de5d08e..abb62783 100644 --- a/_examples/mvc/grpc-compatible/helloworld/helloworld.proto +++ b/_examples/mvc/grpc-compatible/helloworld/helloworld.proto @@ -14,10 +14,6 @@ syntax = "proto3"; -option java_multiple_files = true; -option java_package = "io.grpc.examples.helloworld"; -option java_outer_classname = "HelloWorldProto"; - package helloworld; // The greeting service definition. diff --git a/core/host/proxy.go b/core/host/proxy.go index a6c79b6f..2f9ecee1 100644 --- a/core/host/proxy.go +++ b/core/host/proxy.go @@ -84,6 +84,19 @@ func NewProxy(hostAddr string, target *url.URL) *Supervisor { // 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 { + redirectSrv := &http.Server{ + ReadTimeout: 30 * time.Second, + WriteTimeout: 60 * time.Second, + Addr: hostAddr, + Handler: RedirectHandler(target, redirectStatus), + } + + return New(redirectSrv) +} + +// RedirectHandler returns a simple redirect handler. +// See `NewProxy` or `ProxyHandler` for more features. +func RedirectHandler(target *url.URL, redirectStatus int) http.Handler { targetURI := target.String() if redirectStatus <= 300 { // here we should use StatusPermanentRedirect but @@ -96,18 +109,11 @@ func NewRedirection(hostAddr string, target *url.URL, redirectStatus int) *Super 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) + return 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) + }) } diff --git a/core/host/supervisor.go b/core/host/supervisor.go index a90fa2ff..abf02e4f 100644 --- a/core/host/supervisor.go +++ b/core/host/supervisor.go @@ -6,6 +6,8 @@ import ( "errors" "net" "net/http" + "net/url" + "os" "strings" "sync" "sync/atomic" @@ -27,12 +29,13 @@ type Configurator func(su *Supervisor) // // Interfaces are separated to return relative functionality to them. type Supervisor struct { - Server *http.Server - closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown) - closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose. - manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin. - shouldWait int32 // non-zero means that the host should wait for unblocking - unblockChan chan struct{} + Server *http.Server + disableHTTP1ToHTTP2Redirection bool // if true then no secondary server on `ListenAndServeTLS/AutoTLS` will be registered, exposed through `NoRedirect`. + closedManually uint32 // future use, accessed atomically (non-zero means we've called the Shutdown) + closedByInterruptHandler uint32 // non-zero means that the end-developer interrupted it by-purpose. + manuallyTLS bool // we need that in order to determinate what to output on the console before the server begin. + shouldWait int32 // non-zero means that the host should wait for unblocking + unblockChan chan struct{} mu sync.Mutex @@ -77,6 +80,14 @@ func (su *Supervisor) Configure(configurators ...Configurator) *Supervisor { return su } +// NoRedirect should be called before `ListenAndServeTLS/AutoTLS` when +// secondary http1 to http2 server is not required. This method will disable +// the automatic registration of secondary http.Server +// which would redirect "http://" requests to their "https://" equivalent. +func (su *Supervisor) NoRedirect() { + su.disableHTTP1ToHTTP2Redirection = true +} + // DeferFlow defers the flow of the exeuction, // i.e: when server should return error and exit // from app, a DeferFlow call inside a Task @@ -234,38 +245,58 @@ func (su *Supervisor) ListenAndServe() error { return su.Serve(l) } +func loadCertificate(c, k string) (*tls.Certificate, error) { + var ( + cert tls.Certificate + err error + ) + + if fileExists(c) && fileExists(k) { + // act them as files in the system. + cert, err = tls.LoadX509KeyPair(c, k) + } else { + // act them as raw contents. + cert, err = tls.X509KeyPair([]byte(c), []byte(k)) + } + + if err != nil { + return nil, err + } + + return &cert, nil +} + // 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 { - su.manuallyTLS = true +func (su *Supervisor) ListenAndServeTLS(certFileOrContents string, keyFileOrContents string) error { + var getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error) - 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 { + // If tls.Config configured manually through a host configurator then skip that + // and let the redirection service registered alone. + // e.g. https://github.com/kataras/iris/issues/1481#issuecomment-605621255 + if su.Server.TLSConfig == nil { + if certFileOrContents == "" && keyFileOrContents == "" { + return errors.New("empty certFileOrContents or keyFileOrContents and Server.TLSConfig") + } + + cert, err := loadCertificate(certFileOrContents, keyFileOrContents) + if err != nil { return err } - // manually inserted as pre-go 1.9 for any case. - cfg.NextProtos = []string{"h2", "http/1.1"} - su.Server.TLSConfig = cfg - - // It does nothing more than the su.Server.ListenAndServeTLS anymore. - // - no hurt if we let it as it is - // - no problem if we remove it as well - // but let's comment this as proposed, fewer code is better: - // return su.ListenAndServe() + getCertificate = func(*tls.ClientHelloInfo) (*tls.Certificate, error) { + return cert, nil + } } - if su.Server.TLSConfig == nil { - return errors.New("empty certFile or keyFile and Server.TLSConfig") - } + target, _ := url.Parse("https://" + netutil.ResolveVHost(su.Server.Addr)) // e.g. https://localhost:443 + http1Handler := RedirectHandler(target, http.StatusMovedPermanently) - return su.supervise(func() error { return su.Server.ListenAndServeTLS("", "") }) + su.manuallyTLS = true + return su.runTLS(getCertificate, http1Handler) } // ListenAndServeAutoTLS acts identically to ListenAndServe, except that it @@ -310,40 +341,67 @@ func (su *Supervisor) ListenAndServeAutoTLS(domain string, email string, cacheDi HostPolicy: hostPolicy, Email: email, Cache: cache, - ForceRSA: true, } - srv2 := &http.Server{ - ReadTimeout: 30 * time.Second, - WriteTimeout: 60 * time.Second, - Addr: ":http", - Handler: autoTLSManager.HTTPHandler(nil), // nil for redirect. + return su.runTLS(autoTLSManager.GetCertificate, autoTLSManager.HTTPHandler(nil /* nil for redirect */)) +} + +func (su *Supervisor) runTLS(getCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error), http1Handler http.Handler) error { + if !su.disableHTTP1ToHTTP2Redirection && http1Handler != nil { + // Note: no need to use a function like ping(":http") to see + // if there is another server running, if it is + // then this server will errored and not start at all. + http1RedirectServer := &http.Server{ + ReadTimeout: 30 * time.Second, + WriteTimeout: 60 * time.Second, + Addr: ":http", + Handler: http1Handler, + } + + // 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 := 10 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + http1RedirectServer.Shutdown(ctx) + }) + go http1RedirectServer.ListenAndServe() } - // 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 := 10 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - srv2.Shutdown(ctx) - }) - go srv2.ListenAndServe() + if su.Server.TLSConfig == nil { + // If tls.Config is NOT configured manually through a host configurator, + // then create it. + su.Server.TLSConfig = &tls.Config{ - su.Server.TLSConfig = &tls.Config{ - MinVersion: tls.VersionTLS10, - GetCertificate: autoTLSManager.GetCertificate, - PreferServerCipherSuites: true, - // Keep the defaults. - CurvePreferences: []tls.CurveID{ - tls.X25519, - tls.CurveP256, - tls.CurveP384, - tls.CurveP521, - }, + MinVersion: tls.VersionTLS12, + GetCertificate: getCertificate, + PreferServerCipherSuites: true, + NextProtos: []string{"h2", "http/1.1"}, + CurvePreferences: []tls.CurveID{ + tls.CurveP521, + tls.CurveP384, + tls.CurveP256, + }, + CipherSuites: []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + 0xC028, /* TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 */ + }, + } } - return su.ListenAndServeTLS("", "") + + return su.supervise(func() error { return su.Server.ListenAndServeTLS("", "") }) } // RegisterOnShutdown registers a function to call on Shutdown. @@ -377,3 +435,13 @@ func (su *Supervisor) shutdownOnInterrupt(ctx context.Context) { atomic.StoreUint32(&su.closedByInterruptHandler, 1) su.Shutdown(ctx) } + +// fileExists tries to report whether a local physical file of "filename" exists. +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + + return !info.IsDir() +} diff --git a/iris.go b/iris.go index 2b8f7abd..199dea3e 100644 --- a/iris.go +++ b/iris.go @@ -904,15 +904,21 @@ func Addr(addr string, hostConfigs ...host.Configurator) Runner { } } +// TLSNoRedirect is a `host.Configurator` which can be passed as last argument +// to the `TLS` and `AutoTLS` functions. It disables the automatic +// registration of redirection from "http://" to "https://" requests. +var TLSNoRedirect = func(su *host.Supervisor) { su.NoRedirect() } + // TLS can be used as an argument for the `Run` method. // It will start the Application's secure server. // // Use it like you used to use the http.ListenAndServeTLS function. // // Addr should have the form of [host]:port, i.e localhost:443 or :443. -// CertFile & KeyFile should be filenames with their extensions. +// "certFileOrContents" & "keyFileOrContents" should be filenames with their extensions +// or raw contents of the certificate and the private key. // -// Second argument is optional, it accepts one or more +// 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, @@ -922,11 +928,11 @@ func Addr(addr string, hostConfigs ...host.Configurator) Runner { // Look at the `ConfigureHost` too. // // See `Run` for more. -func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator) Runner { +func TLS(addr string, certFileOrContents, keyFileOrContents string, hostConfigs ...host.Configurator) Runner { return func(app *Application) error { return app.NewHost(&http.Server{Addr: addr}). Configure(hostConfigs...). - ListenAndServeTLS(certFile, keyFile) + ListenAndServeTLS(certFileOrContents, keyFileOrContents) } }