1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-04 10:47:20 +00:00

iris.TLS starts a secondary http redirection server now (like AutoTLS did) and add 'iris.TLSNoRedirect' to disable it (on both TLS and AutoTLS)

Former-commit-id: c7a535bf860a67604de3d09ade30599611e096f1
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-05-08 21:13:12 +03:00
parent 116503a9a5
commit b6a36bf28d
7 changed files with 168 additions and 86 deletions

View File

@@ -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)
})
}

View File

@@ -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()
}