diff options
author | Wolfy-J <[email protected]> | 2019-05-04 18:47:12 +0300 |
---|---|---|
committer | Wolfy-J <[email protected]> | 2019-05-04 18:47:12 +0300 |
commit | 17c259e8e0a7988b4336643c4d180733663dbaa3 (patch) | |
tree | 157faa2b009e648ba17fbce86fa6ac2a08ade42a /service | |
parent | 5f0fa44cc1b2ad377eb0832ec7e4f2337fc843b4 (diff) |
real ip + better stop sequence
Diffstat (limited to 'service')
-rw-r--r-- | service/http/config.go | 55 | ||||
-rw-r--r-- | service/http/config_test.go | 49 | ||||
-rw-r--r-- | service/http/handler.go | 25 | ||||
-rw-r--r-- | service/http/request.go | 25 | ||||
-rw-r--r-- | service/http/response.go | 2 | ||||
-rw-r--r-- | service/watcher/watcher.go | 10 |
6 files changed, 151 insertions, 15 deletions
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) + } } } } |