diff options
Diffstat (limited to 'plugins/http')
-rw-r--r-- | plugins/http/config/http.go | 34 | ||||
-rw-r--r-- | plugins/http/config/ssl.go | 69 | ||||
-rw-r--r-- | plugins/http/config/ssl_config_test.go | 116 | ||||
-rw-r--r-- | plugins/http/plugin.go | 61 |
4 files changed, 227 insertions, 53 deletions
diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go index 76547fde..bd689918 100644 --- a/plugins/http/config/http.go +++ b/plugins/http/config/http.go @@ -2,7 +2,6 @@ package config import ( "net" - "os" "runtime" "strings" "time" @@ -13,7 +12,7 @@ import ( // HTTP configures RoadRunner HTTP server. type HTTP struct { - // Port and port to handle as http server. + // Host and port to handle as http server. Address string // SSLConfig defines https server options. @@ -97,8 +96,8 @@ func (c *HTTP) InitDefaults() error { c.SSLConfig = &SSL{} } - if c.SSLConfig.Port == 0 { - c.SSLConfig.Port = 443 + if c.SSLConfig.Address == "" { + c.SSLConfig.Address = ":443" } err := c.HTTP2Config.InitDefaults() @@ -191,30 +190,9 @@ func (c *HTTP) Valid() error { } if c.EnableTLS() { - if _, err := os.Stat(c.SSLConfig.Key); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("key file '%s' does not exists", c.SSLConfig.Key)) - } - - return err - } - - if _, err := os.Stat(c.SSLConfig.Cert); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("cert file '%s' does not exists", c.SSLConfig.Cert)) - } - - return err - } - - // RootCA is optional, but if provided - check it - if c.SSLConfig.RootCA != "" { - if _, err := os.Stat(c.SSLConfig.RootCA); err != nil { - if os.IsNotExist(err) { - return errors.E(op, errors.Errorf("root ca path provided, but path '%s' does not exists", c.SSLConfig.RootCA)) - } - return err - } + err := c.SSLConfig.Valid() + if err != nil { + return errors.E(op, err) } } diff --git a/plugins/http/config/ssl.go b/plugins/http/config/ssl.go index aae6e920..c33dbce4 100644 --- a/plugins/http/config/ssl.go +++ b/plugins/http/config/ssl.go @@ -1,9 +1,17 @@ package config +import ( + "os" + "strconv" + "strings" + + "github.com/spiral/errors" +) + // SSL defines https server configuration. type SSL struct { - // Port to listen as HTTPS server, defaults to 443. - Port int + // Address to listen as HTTPS server, defaults to 0.0.0.0:443. + Address string // Redirect when enabled forces all http connections to switch to https. Redirect bool @@ -16,4 +24,61 @@ type SSL struct { // Root CA file RootCA string + + // internal + host string + Port int +} + +func (s *SSL) Valid() error { + const op = errors.Op("ssl_valid") + + parts := strings.Split(s.Address, ":") + switch len(parts) { + // :443 form + // localhost:443 form + // use 0.0.0.0 as host and 443 as port + case 2: + if parts[0] == "" { + s.host = "0.0.0.0" + } else { + s.host = parts[0] + } + + port, err := strconv.Atoi(parts[1]) + if err != nil { + return errors.E(op, err) + } + s.Port = port + default: + return errors.E(op, errors.Errorf("unknown format, accepted format is [:<port> or <host>:<port>], provided: %s", s.Address)) + } + + if _, err := os.Stat(s.Key); err != nil { + if os.IsNotExist(err) { + return errors.E(op, errors.Errorf("key file '%s' does not exists", s.Key)) + } + + return err + } + + if _, err := os.Stat(s.Cert); err != nil { + if os.IsNotExist(err) { + return errors.E(op, errors.Errorf("cert file '%s' does not exists", s.Cert)) + } + + return err + } + + // RootCA is optional, but if provided - check it + if s.RootCA != "" { + if _, err := os.Stat(s.RootCA); err != nil { + if os.IsNotExist(err) { + return errors.E(op, errors.Errorf("root ca path provided, but path '%s' does not exists", s.RootCA)) + } + return err + } + } + + return nil } diff --git a/plugins/http/config/ssl_config_test.go b/plugins/http/config/ssl_config_test.go new file mode 100644 index 00000000..1f5fef0a --- /dev/null +++ b/plugins/http/config/ssl_config_test.go @@ -0,0 +1,116 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSSL_Valid1(t *testing.T) { + conf := &SSL{ + Address: "", + Redirect: false, + Key: "", + Cert: "", + RootCA: "", + host: "", + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} + +func TestSSL_Valid2(t *testing.T) { + conf := &SSL{ + Address: ":hello", + Redirect: false, + Key: "", + Cert: "", + RootCA: "", + host: "", + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} + +func TestSSL_Valid3(t *testing.T) { + conf := &SSL{ + Address: ":555", + Redirect: false, + Key: "", + Cert: "", + RootCA: "", + host: "", + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} + +func TestSSL_Valid4(t *testing.T) { + conf := &SSL{ + Address: ":555", + Redirect: false, + Key: "../../../tests/plugins/http/fixtures/server.key", + Cert: "../../../tests/plugins/http/fixtures/server.crt", + RootCA: "", + host: "", + // private + Port: 0, + } + + err := conf.Valid() + assert.NoError(t, err) +} + +func TestSSL_Valid5(t *testing.T) { + conf := &SSL{ + Address: "a:b:c", + Redirect: false, + Key: "../../../tests/plugins/http/fixtures/server.key", + Cert: "../../../tests/plugins/http/fixtures/server.crt", + RootCA: "", + host: "", + // private + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} + +func TestSSL_Valid6(t *testing.T) { + conf := &SSL{ + Address: ":", + Redirect: false, + Key: "../../../tests/plugins/http/fixtures/server.key", + Cert: "../../../tests/plugins/http/fixtures/server.crt", + RootCA: "", + host: "", + // private + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} + +func TestSSL_Valid7(t *testing.T) { + conf := &SSL{ + Address: "localhost:555:1", + Redirect: false, + Key: "../../../tests/plugins/http/fixtures/server.key", + Cert: "../../../tests/plugins/http/fixtures/server.crt", + RootCA: "", + host: "", + // private + Port: 0, + } + + err := conf.Valid() + assert.Error(t, err) +} diff --git a/plugins/http/plugin.go b/plugins/http/plugin.go index 35acd2b7..249d2e57 100644 --- a/plugins/http/plugin.go +++ b/plugins/http/plugin.go @@ -36,6 +36,9 @@ const ( // RR_HTTP env variable key (internal) if the HTTP presents RR_HTTP = "RR_HTTP" //nolint:golint,stylecheck + + // HTTPS_SCHEME + HTTPS_SCHEME = "https" //nolint:golint,stylecheck ) // Middleware interface @@ -154,9 +157,9 @@ func (s *Plugin) Serve() chan error { if s.cfg.EnableHTTP() { if s.cfg.EnableH2C() { - s.http = &http.Server{Addr: s.cfg.Address, Handler: h2c.NewHandler(s, &http2.Server{})} + s.http = &http.Server{Handler: h2c.NewHandler(s, &http2.Server{})} } else { - s.http = &http.Server{Addr: s.cfg.Address, Handler: s} + s.http = &http.Server{Handler: s} } } @@ -190,9 +193,15 @@ func (s *Plugin) Serve() chan error { if s.http != nil { go func() { - httpErr := s.http.ListenAndServe() - if httpErr != nil && httpErr != http.ErrServerClosed { - errCh <- errors.E(op, httpErr) + l, err := utils.CreateListener(s.cfg.Address) + if err != nil { + errCh <- errors.E(op, err) + return + } + + err = s.http.Serve(l) + if err != nil && err != http.ErrServerClosed { + errCh <- errors.E(op, err) return } }() @@ -200,13 +209,20 @@ func (s *Plugin) Serve() chan error { if s.https != nil { go func() { - httpErr := s.https.ListenAndServeTLS( + l, err := utils.CreateListener(s.cfg.SSLConfig.Address) + if err != nil { + errCh <- errors.E(op, err) + return + } + + err = s.https.ServeTLS( + l, s.cfg.SSLConfig.Cert, s.cfg.SSLConfig.Key, ) - if httpErr != nil && httpErr != http.ErrServerClosed { - errCh <- errors.E(op, httpErr) + if err != nil && err != http.ErrServerClosed { + errCh <- errors.E(op, err) return } }() @@ -270,7 +286,8 @@ func (s *Plugin) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if s.redirect(w, r) { + if s.https != nil && r.TLS == nil && s.cfg.SSLConfig.Redirect { + s.redirect(w, r) return } @@ -362,21 +379,19 @@ func (s *Plugin) Status() checker.Status { } } -func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) bool { - if s.https != nil && r.TLS == nil && s.cfg.SSLConfig.Redirect { - target := &url.URL{ - Scheme: "https", - Host: s.tlsAddr(r.Host, false), - Path: r.URL.Path, - RawQuery: r.URL.RawQuery, - } - - http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect) - return true +func (s *Plugin) redirect(w http.ResponseWriter, r *http.Request) { + target := &url.URL{ + Scheme: HTTPS_SCHEME, + // host or host:port + Host: s.tlsAddr(r.Host, false), + Path: r.URL.Path, + RawQuery: r.URL.RawQuery, } - return false + + http.Redirect(w, r, target.String(), http.StatusTemporaryRedirect) } +//go:inline func headerContainsUpgrade(r *http.Request, s *Plugin) bool { if _, ok := r.Header["Upgrade"]; ok { // https://golang.org/pkg/net/http/#Hijacker @@ -468,7 +483,7 @@ func (s *Plugin) initSSL() *http.Server { DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...) DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...) - server := &http.Server{ + sslServer := &http.Server{ Addr: s.tlsAddr(s.cfg.Address, true), Handler: s, TLSConfig: &tls.Config{ @@ -484,7 +499,7 @@ func (s *Plugin) initSSL() *http.Server { }, } - return server + return sslServer } // init http/2 server |