diff options
Diffstat (limited to 'plugins/http/config')
-rw-r--r-- | plugins/http/config/fcgi.go | 7 | ||||
-rw-r--r-- | plugins/http/config/http.go | 180 | ||||
-rw-r--r-- | plugins/http/config/http2.go | 28 | ||||
-rw-r--r-- | plugins/http/config/ip.go | 26 | ||||
-rw-r--r-- | plugins/http/config/ssl.go | 84 | ||||
-rw-r--r-- | plugins/http/config/ssl_config_test.go | 116 | ||||
-rw-r--r-- | plugins/http/config/uploads_config.go | 46 |
7 files changed, 487 insertions, 0 deletions
diff --git a/plugins/http/config/fcgi.go b/plugins/http/config/fcgi.go new file mode 100644 index 00000000..3d4acbe1 --- /dev/null +++ b/plugins/http/config/fcgi.go @@ -0,0 +1,7 @@ +package config + +// FCGI for FastCGI server. +type FCGI struct { + // Address and port to handle as http server. + Address string +} diff --git a/plugins/http/config/http.go b/plugins/http/config/http.go new file mode 100644 index 00000000..bfbc1af6 --- /dev/null +++ b/plugins/http/config/http.go @@ -0,0 +1,180 @@ +package config + +import ( + "net" + "runtime" + "strings" + "time" + + "github.com/spiral/errors" + poolImpl "github.com/spiral/roadrunner/v2/pkg/pool" +) + +// HTTP configures RoadRunner HTTP server. +type HTTP struct { + // Host and port to handle as http server. + Address string + + // SSLConfig defines https server options. + SSLConfig *SSL `mapstructure:"ssl"` + + // FCGIConfig configuration. You can use FastCGI without HTTP server. + FCGIConfig *FCGI `mapstructure:"fcgi"` + + // HTTP2Config configuration + HTTP2Config *HTTP2 `mapstructure:"http2"` + + // MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited. + MaxRequestSize uint64 `mapstructure:"max_request_size"` + + // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For + TrustedSubnets []string `mapstructure:"trusted_subnets"` + + // Uploads configures uploads configuration. + Uploads *Uploads `mapstructure:"uploads"` + + // Pool configures worker pool. + Pool *poolImpl.Config `mapstructure:"pool"` + + // Env is environment variables passed to the http pool + Env map[string]string + + // List of the middleware names (order will be preserved) + Middleware []string + + // slice of net.IPNet + Cidrs Cidrs +} + +// EnableHTTP is true when http server must run. +func (c *HTTP) EnableHTTP() bool { + return c.Address != "" +} + +// EnableTLS returns true if pool must listen TLS connections. +func (c *HTTP) EnableTLS() bool { + return c.SSLConfig.Key != "" || c.SSLConfig.Cert != "" || c.SSLConfig.RootCA != "" +} + +// EnableH2C when HTTP/2 extension must be enabled on TCP. +func (c *HTTP) EnableH2C() bool { + return c.HTTP2Config.H2C +} + +// EnableFCGI is true when FastCGI server must be enabled. +func (c *HTTP) EnableFCGI() bool { + return c.FCGIConfig.Address != "" +} + +// InitDefaults must populate HTTP values using given HTTP source. Must return error if HTTP is not valid. +func (c *HTTP) InitDefaults() error { + if c.Pool == nil { + // default pool + c.Pool = &poolImpl.Config{ + Debug: false, + NumWorkers: uint64(runtime.NumCPU()), + MaxJobs: 1000, + AllocateTimeout: time.Second * 60, + DestroyTimeout: time.Second * 60, + Supervisor: nil, + } + } + + if c.HTTP2Config == nil { + c.HTTP2Config = &HTTP2{} + } + + if c.FCGIConfig == nil { + c.FCGIConfig = &FCGI{} + } + + if c.Uploads == nil { + c.Uploads = &Uploads{} + } + + if c.SSLConfig == nil { + c.SSLConfig = &SSL{} + } + + if c.SSLConfig.Address == "" { + c.SSLConfig.Address = ":443" + } + + err := c.HTTP2Config.InitDefaults() + if err != nil { + return err + } + err = c.Uploads.InitDefaults() + if err != nil { + return err + } + + if c.TrustedSubnets == nil { + // @see https://en.wikipedia.org/wiki/Reserved_IP_addresses + c.TrustedSubnets = []string{ + "10.0.0.0/8", + "127.0.0.0/8", + "172.16.0.0/12", + "192.168.0.0/16", + "::1/128", + "fc00::/7", + "fe80::/10", + } + } + + cidrs, err := ParseCIDRs(c.TrustedSubnets) + if err != nil { + return err + } + c.Cidrs = cidrs + + return c.Valid() +} + +// ParseCIDRs parse IPNet addresses and return slice of its +func ParseCIDRs(subnets []string) (Cidrs, error) { + c := make(Cidrs, 0, len(subnets)) + for _, cidr := range subnets { + _, cr, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + + c = append(c, cr) + } + + return c, nil +} + +// Valid validates the configuration. +func (c *HTTP) Valid() error { + const op = errors.Op("validation") + if c.Uploads == nil { + return errors.E(op, errors.Str("malformed uploads config")) + } + + if c.HTTP2Config == nil { + return errors.E(op, errors.Str("malformed http2 config")) + } + + if c.Pool == nil { + return errors.E(op, "malformed pool config") + } + + if !c.EnableHTTP() && !c.EnableTLS() && !c.EnableFCGI() { + return errors.E(op, errors.Str("unable to run http service, no method has been specified (http, https, http/2 or FastCGI)")) + } + + if c.Address != "" && !strings.Contains(c.Address, ":") { + return errors.E(op, errors.Str("malformed http server address")) + } + + if c.EnableTLS() { + err := c.SSLConfig.Valid() + if err != nil { + return errors.E(op, err) + } + } + + return nil +} diff --git a/plugins/http/config/http2.go b/plugins/http/config/http2.go new file mode 100644 index 00000000..b1e109e9 --- /dev/null +++ b/plugins/http/config/http2.go @@ -0,0 +1,28 @@ +package config + +// HTTP2 HTTP/2 server customizations. +type HTTP2 struct { + // h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic + // that should be h2c traffic. There are two ways to begin a h2c connection + // (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this + // works by starting an h2c connection with a string of bytes that is valid + // HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to + // h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to + // h2c. When either of those situations occur we hijack the HTTP/1 connection, + // convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn. + + // H2C enables HTTP/2 over TCP + H2C bool + + // MaxConcurrentStreams defaults to 128. + MaxConcurrentStreams uint32 `mapstructure:"max_concurrent_streams"` +} + +// InitDefaults sets default values for HTTP/2 configuration. +func (cfg *HTTP2) InitDefaults() error { + if cfg.MaxConcurrentStreams == 0 { + cfg.MaxConcurrentStreams = 128 + } + + return nil +} diff --git a/plugins/http/config/ip.go b/plugins/http/config/ip.go new file mode 100644 index 00000000..c4981f74 --- /dev/null +++ b/plugins/http/config/ip.go @@ -0,0 +1,26 @@ +package config + +import "net" + +// Cidrs is a slice of IPNet addresses +type Cidrs []*net.IPNet + +// IsTrusted checks if the ip address exists in the provided in the config addresses +func (c *Cidrs) IsTrusted(ip string) bool { + if len(*c) == 0 { + return false + } + + i := net.ParseIP(ip) + if i == nil { + return false + } + + for _, cird := range *c { + if cird.Contains(i) { + return true + } + } + + return false +} diff --git a/plugins/http/config/ssl.go b/plugins/http/config/ssl.go new file mode 100644 index 00000000..eb2b72b5 --- /dev/null +++ b/plugins/http/config/ssl.go @@ -0,0 +1,84 @@ +package config + +import ( + "os" + "strconv" + "strings" + + "github.com/spiral/errors" +) + +// SSL defines https server configuration. +type SSL struct { + // 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 + + // Key defined private server key. + Key string + + // Cert is https certificate. + Cert string + + // Root CA file + RootCA string `mapstructure:"root_ca"` + + // 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/config/uploads_config.go b/plugins/http/config/uploads_config.go new file mode 100644 index 00000000..5edb0ab7 --- /dev/null +++ b/plugins/http/config/uploads_config.go @@ -0,0 +1,46 @@ +package config + +import ( + "os" + "path" + "strings" +) + +// Uploads describes file location and controls access to them. +type Uploads struct { + // Dir contains name of directory to control access to. + Dir string + + // Forbid specifies list of file extensions which are forbidden for access. + // Example: .php, .exe, .bat, .htaccess and etc. + Forbid []string +} + +// InitDefaults sets missing values to their default values. +func (cfg *Uploads) InitDefaults() error { + cfg.Forbid = []string{".php", ".exe", ".bat"} + cfg.Dir = os.TempDir() + return nil +} + +// TmpDir returns temporary directory. +func (cfg *Uploads) TmpDir() string { + if cfg.Dir != "" { + return cfg.Dir + } + + return os.TempDir() +} + +// Forbids must return true if file extension is not allowed for the upload. +func (cfg *Uploads) Forbids(filename string) bool { + ext := strings.ToLower(path.Ext(filename)) + + for _, v := range cfg.Forbid { + if ext == v { + return true + } + } + + return false +} |