summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWolfy-J <[email protected]>2019-05-04 18:47:12 +0300
committerWolfy-J <[email protected]>2019-05-04 18:47:12 +0300
commit17c259e8e0a7988b4336643c4d180733663dbaa3 (patch)
tree157faa2b009e648ba17fbce86fa6ac2a08ade42a
parent5f0fa44cc1b2ad377eb0832ec7e4f2337fc843b4 (diff)
real ip + better stop sequence
-rw-r--r--CHANGELOG.md9
-rw-r--r--go.mod1
-rw-r--r--service/http/config.go55
-rw-r--r--service/http/config_test.go49
-rw-r--r--service/http/handler.go25
-rw-r--r--service/http/request.go25
-rw-r--r--service/http/response.go2
-rw-r--r--service/watcher/watcher.go10
8 files changed, 157 insertions, 19 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 22d3bf85..fcfdf990 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,12 @@ v1.4.0
- ENV variables in configs (automatic RR_ mapping and manual definition using "${ENV_NAME}" value)
- add the ability to remove the worker from the pool in runtime
- minor performance improvements
+- real ip resolution using X-Real-Ip and X-Forwarded-For (+cidr verification)
- watchers
- - maxTTL
- - maxExecTTL (max_execution_time)
- - maxIdleTTL
- - maxMemory (RSS)
+ - maxTTL (graceful)
+ - maxExecTTL (brute, max_execution_time)
+ - maxIdleTTL (graceful)
+ - maxMemory (graceful)
- stop command
- `maxRequest` option has been deprecated in favor of `maxRequestSize`
- download rr command (symfony/console based) by @Alex-Bond
diff --git a/go.mod b/go.mod
index 2b04eb3a..fb8f5133 100644
--- a/go.mod
+++ b/go.mod
@@ -22,6 +22,7 @@ require (
github.com/spf13/viper v1.3.1
github.com/spiral/goridge v2.1.3+incompatible
github.com/stretchr/testify v1.2.2
+ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/net v0.0.0-20181017193950-04a2e542c03f
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
)
diff --git a/service/http/config.go b/service/http/config.go
index 899a5083..165b45de 100644
--- a/service/http/config.go
+++ b/service/http/config.go
@@ -5,6 +5,7 @@ import (
"fmt"
"github.com/spiral/roadrunner"
"github.com/spiral/roadrunner/service"
+ "net"
"os"
"strings"
)
@@ -20,6 +21,10 @@ type Config struct {
// MaxRequestSize specified max size for payload body in megabytes, set 0 to unlimited.
MaxRequestSize int64
+ // TrustedSubnets declare IP subnets which are allowed to set ip using X-Real-Ip and X-Forwarded-For
+ TrustedSubnets []string
+ cidrs []*net.IPNet
+
// Uploads configures uploads configuration.
Uploads *UploadsConfig
@@ -70,9 +75,59 @@ func (c *Config) Hydrate(cfg service.Config) error {
c.Workers.UpscaleDurations()
+ 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",
+ }
+ }
+
+ if err := c.parseCIDRs(); err != nil {
+ return err
+ }
+
return c.Valid()
}
+func (c *Config) parseCIDRs() error {
+ for _, cidr := range c.TrustedSubnets {
+ _, cr, err := net.ParseCIDR(cidr)
+ if err != nil {
+ return err
+ }
+
+ c.cidrs = append(c.cidrs, cr)
+ }
+
+ return nil
+}
+
+// IsTrusted if api can be trusted to use X-Real-Ip, X-Forwarded-For
+func (c *Config) IsTrusted(ip string) bool {
+ if c.cidrs == nil {
+ return false
+ }
+
+ i := net.ParseIP(ip)
+ if i == nil {
+ return false
+ }
+
+ for _, cird := range c.cidrs {
+ if cird.Contains(i) {
+ return true
+ }
+ }
+
+ return false
+}
+
// Valid validates the configuration.
func (c *Config) Valid() error {
if c.Uploads == nil {
diff --git a/service/http/config_test.go b/service/http/config_test.go
index 4cd2783f..48651e16 100644
--- a/service/http/config_test.go
+++ b/service/http/config_test.go
@@ -51,6 +51,55 @@ func Test_Config_Valid(t *testing.T) {
assert.NoError(t, cfg.Valid())
}
+func Test_Trusted_Subnets(t *testing.T) {
+ cfg := &Config{
+ Address: ":8080",
+ MaxRequestSize: 1024,
+ Uploads: &UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{".go"},
+ },
+ TrustedSubnets: []string{"200.1.0.0/16"},
+ Workers: &roadrunner.ServerConfig{
+ Command: "php tests/client.php echo pipes",
+ Relay: "pipes",
+ Pool: &roadrunner.Config{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second,
+ DestroyTimeout: time.Second,
+ },
+ },
+ }
+
+ assert.NoError(t, cfg.parseCIDRs())
+
+ assert.True(t, cfg.IsTrusted("200.1.0.10"))
+ assert.False(t, cfg.IsTrusted("127.0.0.0.1"))
+}
+
+func Test_Trusted_Subnets_Err(t *testing.T) {
+ cfg := &Config{
+ Address: ":8080",
+ MaxRequestSize: 1024,
+ Uploads: &UploadsConfig{
+ Dir: os.TempDir(),
+ Forbid: []string{".go"},
+ },
+ TrustedSubnets: []string{"200.1.0.0"},
+ Workers: &roadrunner.ServerConfig{
+ Command: "php tests/client.php echo pipes",
+ Relay: "pipes",
+ Pool: &roadrunner.Config{
+ NumWorkers: 1,
+ AllocateTimeout: time.Second,
+ DestroyTimeout: time.Second,
+ },
+ },
+ }
+
+ assert.Error(t, cfg.parseCIDRs())
+}
+
func Test_Config_Valid_SSL(t *testing.T) {
cfg := &Config{
Address: ":8080",
diff --git a/service/http/handler.go b/service/http/handler.go
index a7a6d4d0..280d67aa 100644
--- a/service/http/handler.go
+++ b/service/http/handler.go
@@ -5,6 +5,7 @@ import (
"github.com/spiral/roadrunner"
"net/http"
"strconv"
+ "strings"
"sync"
"time"
)
@@ -93,6 +94,9 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
+ // proxy IP resolution
+ h.resolveIP(req)
+
req.Open()
defer req.Close()
@@ -140,3 +144,24 @@ func (h *Handler) throw(event int, ctx interface{}) {
h.lsn(event, ctx)
}
}
+
+// get real ip passing multiple proxy
+func (h *Handler) resolveIP(r *Request) {
+ if !h.cfg.IsTrusted(r.RemoteAddr) {
+ return
+ }
+
+ if r.Header.Get("X-Forwarded-For") != "" {
+ for _, addr := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
+ addr = strings.TrimSpace(addr)
+ if h.cfg.IsTrusted(addr) {
+ r.RemoteAddr = addr
+ }
+ }
+ return
+ }
+
+ if r.Header.Get("X-Real-Ip") != "" {
+ r.RemoteAddr = fetchIP(r.Header.Get("X-Real-Ip"))
+ }
+}
diff --git a/service/http/request.go b/service/http/request.go
index b1ca514a..e56acb2a 100644
--- a/service/http/request.go
+++ b/service/http/request.go
@@ -34,8 +34,8 @@ type Request struct {
// URI contains full request URI with scheme and query.
URI string `json:"uri"`
- // Headers contains list of request headers.
- Headers http.Header `json:"headers"`
+ // Header contains list of request headers.
+ Header http.Header `json:"headers"`
// Cookies contains list of request cookies.
Cookies map[string]string `json:"cookies"`
@@ -56,25 +56,28 @@ type Request struct {
body interface{}
}
+func fetchIP(pair string) string {
+ if !strings.ContainsRune(pair, ':') {
+ return pair
+ }
+
+ addr, _, _ := net.SplitHostPort(pair)
+ return addr
+}
+
// NewRequest creates new PSR7 compatible request using net/http request.
func NewRequest(r *http.Request, cfg *UploadsConfig) (req *Request, err error) {
req = &Request{
+ RemoteAddr: fetchIP(r.RemoteAddr),
Protocol: r.Proto,
Method: r.Method,
URI: uri(r),
- Headers: r.Header,
+ Header: r.Header,
Cookies: make(map[string]string),
RawQuery: r.URL.RawQuery,
Attributes: attributes.All(r),
}
- // otherwise, return remote address as is
- if strings.ContainsRune(r.RemoteAddr, ':') {
- req.RemoteAddr, _, _ = net.SplitHostPort(r.RemoteAddr)
- } else {
- req.RemoteAddr = r.RemoteAddr
- }
-
for _, c := range r.Cookies() {
if v, err := url.QueryUnescape(c.Value); err == nil {
req.Cookies[c.Name] = v
@@ -152,7 +155,7 @@ func (r *Request) contentType() int {
return contentNone
}
- ct := r.Headers.Get("content-type")
+ ct := r.Header.Get("content-type")
if strings.Contains(ct, "application/x-www-form-urlencoded") {
return contentFormData
}
diff --git a/service/http/response.go b/service/http/response.go
index eb8ce32b..2d17278d 100644
--- a/service/http/response.go
+++ b/service/http/response.go
@@ -12,7 +12,7 @@ type Response struct {
// Status contains response status.
Status int `json:"status"`
- // Headers contains list of response headers.
+ // Header contains list of response headers.
Headers map[string][]string `json:"headers"`
// associated body payload.
diff --git a/service/watcher/watcher.go b/service/watcher/watcher.go
index 08d477fa..e92d0677 100644
--- a/service/watcher/watcher.go
+++ b/service/watcher/watcher.go
@@ -92,11 +92,15 @@ func (wch *watcher) watch(p roadrunner.Pool) {
roadrunner.StateWorking,
now.Add(-time.Second*time.Duration(wch.cfg.MaxExecTTL)),
) {
+ eID := w.State().NumExecs()
err := fmt.Errorf("max exec time reached (%vs)", wch.cfg.MaxExecTTL)
+
if p.Remove(w, err) {
- // brutally
- go w.Kill()
- wch.report(EventMaxExecTTL, w, err)
+ // make sure worker still on initial request
+ if w.State().NumExecs() == eID {
+ go w.Kill()
+ wch.report(EventMaxExecTTL, w, err)
+ }
}
}
}