diff --git a/go.mod b/go.mod index dfcd635..60d5eb0 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-pkgz/lgr v0.10.4 github.com/go-pkgz/repeater v1.1.3 - github.com/go-pkgz/rest v1.12.2 + github.com/go-pkgz/rest v1.14.0 github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/handlers v1.5.1 github.com/prometheus/client_golang v1.12.1 diff --git a/go.sum b/go.sum index c80764c..1e114a4 100644 --- a/go.sum +++ b/go.sum @@ -79,8 +79,8 @@ github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw= github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0= github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE= github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw= -github.com/go-pkgz/rest v1.12.2 h1:AQ7uuBmMcufbiUBc4IS8aqDNMvRJlhtRpXLSNRitjVo= -github.com/go-pkgz/rest v1.12.2/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk= +github.com/go-pkgz/rest v1.14.0 h1:brDLCzIGoe0IiUZqRFpsiCVM9m3L88A7z62qS0V9Yfk= +github.com/go-pkgz/rest v1.14.0/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= diff --git a/vendor/github.com/go-pkgz/rest/README.md b/vendor/github.com/go-pkgz/rest/README.md index c78ddee..82ad44e 100644 --- a/vendor/github.com/go-pkgz/rest/README.md +++ b/vendor/github.com/go-pkgz/rest/README.md @@ -93,22 +93,32 @@ Adds the HTTP Deprecation response header, see [draft-dalal-deprecation-header-0 BasicAuth middleware requires basic auth and matches user & passwd with client-provided checker. In case if no basic auth headers returns `StatusUnauthorized`, in case if checker failed - `StatusForbidden` -## Rewrite middleware +### Rewrite middleware Rewrites requests with from->to rule. Supports regex (like nginx) and prevents multiple rewrites. For example `Rewrite("^/sites/(.*)/settings/$", "/sites/settings/$1")` will change request's URL from `/sites/id1/settings/` to `/sites/settings/id1` -## NoCache middleware +### NoCache middleware Sets a number of HTTP headers to prevent a router (handler's) response from being cached by an upstream proxy and/or client. -## Headers middleware +### Headers middleware Sets headers (passed as key:value) to requests. I.e. `rest.Headers("Server:MyServer", "X-Blah:Foo")` -## Gzip middleware +### Gzip middleware Compresses response with gzip. +### RealIP middleware + +RealIP is a middleware that sets a http.Request's RemoteAddr to the results of parsing either the X-Forwarded-For or X-Real-IP headers. + +### Maybe middleware + +Maybe middleware will allow you to change the flow of the middleware stack execution depending on return +value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if +a request does not satisfy the maybeFn logic. + ## Helpers - `rest.Wrap` - converts a list of middlewares to nested handlers calls (in reverse order) @@ -119,6 +129,8 @@ Compresses response with gzip. - `rest.SendErrorJSON` - makes `{error: blah, details: blah}` json body and responds with given error code. Also, adds context to the logged message - `rest.NewErrorLogger` - creates a struct providing shorter form of logger call - `rest.FileServer` - creates a file server for static assets with directory listing disabled +- `realip.Get` - returns client's IP address +- `rest.ParseFromTo` - parses "from" and "to" request's query params with various formats ## Profiler @@ -133,5 +145,6 @@ Profiler is a convenient subrouter used for mounting net/http/pprof, i.e. return r } ``` -It exposes a whole bunch of `/pprof/*` endpoints as well as `/vars`. Builtin support for `onlyIps` allows to restrict access, which is important if it runs on a publicly exposed port. However, counting on IP check only is not that reliable way to limit request and for production use it would be better to add some sort of auth (for example provided `BasicAuth` middleware) or run with a separate http server, exposed to internal ip/port only. + +It exposes a bunch of `/pprof/*` endpoints as well as `/vars`. Builtin support for `onlyIps` allows restricting access, which is important if it runs on a publicly exposed port. However, counting on IP check only is not that reliable way to limit request and for production use it would be better to add some sort of auth (for example provided `BasicAuth` middleware) or run with a separate http server, exposed to internal ip/port only. diff --git a/vendor/github.com/go-pkgz/rest/logger/logger.go b/vendor/github.com/go-pkgz/rest/logger/logger.go index cc7d9e5..dafdd3b 100644 --- a/vendor/github.com/go-pkgz/rest/logger/logger.go +++ b/vendor/github.com/go-pkgz/rest/logger/logger.go @@ -14,6 +14,8 @@ import ( "strconv" "strings" "time" + + "github.com/go-pkgz/rest/realip" ) // Middleware is a logger for rest requests. @@ -103,7 +105,10 @@ func (l *Middleware) Handler(next http.Handler) http.Handler { rawurl = unescURL } - remoteIP := l.remoteIP(r) + remoteIP, err := realip.Get(r) + if err != nil { + remoteIP = "unknown ip" + } if l.ipFn != nil { // mask ip with ipFn remoteIP = l.ipFn(remoteIP) } @@ -169,7 +174,7 @@ func (l *Middleware) formatDefault(r *http.Request, p *logParts) string { } // 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)" -//nolint gosec +// nolint gosec func (l *Middleware) formatApacheCombined(r *http.Request, p *logParts) string { username := "-" if p.user != "" { @@ -291,17 +296,19 @@ func (l *Middleware) sanitizeQuery(rawQuery string) string { return query.Encode() } -// remoteIP gets address from X-Forwarded-For and than from request's remote address -func (l *Middleware) remoteIP(r *http.Request) (remoteIP string) { +// AnonymizeIP is a function to reset the last part of IPv4 to 0. +// from 123.212.12.78 it will make 123.212.12.0 +func AnonymizeIP(ip string) string { + if ip == "" { + return "" + } - if remoteIP = r.Header.Get("X-Forwarded-For"); remoteIP == "" { - remoteIP = r.RemoteAddr + parts := strings.Split(ip, ".") + if len(parts) != 4 { + return ip } - remoteIP = strings.Split(remoteIP, ":")[0] - if strings.HasPrefix(remoteIP, "[") { - remoteIP = strings.Split(remoteIP, "]:")[0] + "]" - } - return remoteIP + + return strings.Join(parts[:3], ".") + ".0" } // customResponseWriter is an HTTP response logger that keeps HTTP status code and diff --git a/vendor/github.com/go-pkgz/rest/middleware.go b/vendor/github.com/go-pkgz/rest/middleware.go index 7e09e7e..3adb154 100644 --- a/vendor/github.com/go-pkgz/rest/middleware.go +++ b/vendor/github.com/go-pkgz/rest/middleware.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/go-pkgz/rest/logger" + "github.com/go-pkgz/rest/realip" ) // Wrap converts a list of middlewares to nested calls (in reverse order) @@ -93,7 +94,7 @@ func Headers(headers ...string) func(http.Handler) http.Handler { // Maybe middleware will allow you to change the flow of the middleware stack execution depending on return // value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if -// a request does not satisfied the maybeFn logic. +// a request does not satisfy the maybeFn logic. // borrowed from https://github.com/go-chi/chi/blob/master/middleware/maybe.go func Maybe(mw func(http.Handler) http.Handler, maybeFn func(r *http.Request) bool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { @@ -106,3 +107,21 @@ func Maybe(mw func(http.Handler) http.Handler, maybeFn func(r *http.Request) boo }) } } + +// RealIP is a middleware that sets a http.Request's RemoteAddr to the results +// of parsing either the X-Forwarded-For or X-Real-IP headers. +// +// This middleware should only be used if user can trust the headers sent with request. +// If reverse proxies are configured to pass along arbitrary header values from the client, +// or if this middleware used without a reverse proxy, malicious clients could set anything +// as X-Forwarded-For header and attack the server in various ways. +func RealIP(h http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + if rip, err := realip.Get(r); err == nil { + r.RemoteAddr = rip + } + h.ServeHTTP(w, r) + } + + return http.HandlerFunc(fn) +} diff --git a/vendor/github.com/go-pkgz/rest/realip/real.go b/vendor/github.com/go-pkgz/rest/realip/real.go new file mode 100644 index 0000000..c30e3d4 --- /dev/null +++ b/vendor/github.com/go-pkgz/rest/realip/real.go @@ -0,0 +1,80 @@ +// Package realip extracts a real IP address from the request. +package realip + +import ( + "bytes" + "fmt" + "net" + "net/http" + "strings" +) + +type ipRange struct { + start net.IP + end net.IP +} + +var privateRanges = []ipRange{ + {start: net.ParseIP("10.0.0.0"), end: net.ParseIP("10.255.255.255")}, + {start: net.ParseIP("100.64.0.0"), end: net.ParseIP("100.127.255.255")}, + {start: net.ParseIP("172.16.0.0"), end: net.ParseIP("172.31.255.255")}, + {start: net.ParseIP("192.0.0.0"), end: net.ParseIP("192.0.0.255")}, + {start: net.ParseIP("192.168.0.0"), end: net.ParseIP("192.168.255.255")}, + {start: net.ParseIP("198.18.0.0"), end: net.ParseIP("198.19.255.255")}, +} + +// Get returns real ip from the given request +func Get(r *http.Request) (string, error) { + + for _, h := range []string{"X-Forwarded-For", "X-Real-Ip"} { + addresses := strings.Split(r.Header.Get(h), ",") + // march from right to left until we get a public address + // that will be the address right before our proxy. + for i := len(addresses) - 1; i >= 0; i-- { + ip := strings.TrimSpace(addresses[i]) + realIP := net.ParseIP(ip) + if !realIP.IsGlobalUnicast() || isPrivateSubnet(realIP) { + continue + } + return ip, nil + } + } + + // X-Forwarded-For header set but parsing failed above + if r.Header.Get("X-Forwarded-For") != "" { + return "", fmt.Errorf("no valid ip found") + } + + // get IP from RemoteAddr + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return "", fmt.Errorf("can't parse ip %q: %w", r.RemoteAddr, err) + } + if netIP := net.ParseIP(ip); netIP == nil { + return "", fmt.Errorf("no valid ip found") + } + + return ip, nil +} + +// inRange - check to see if a given ip address is within a range given +func inRange(r ipRange, ipAddress net.IP) bool { + // strcmp type byte comparison + if bytes.Compare(ipAddress, r.start) >= 0 && bytes.Compare(ipAddress, r.end) < 0 { + return true + } + return false +} + +// isPrivateSubnet - check to see if this ip is in a private subnet +func isPrivateSubnet(ipAddress net.IP) bool { + if ipCheck := ipAddress.To4(); ipCheck != nil { + for _, r := range privateRanges { + // check if this ip is in a private range + if inRange(r, ipAddress) { + return true + } + } + } + return false +} diff --git a/vendor/github.com/go-pkgz/rest/rest.go b/vendor/github.com/go-pkgz/rest/rest.go index 82361bc..57fe4a7 100644 --- a/vendor/github.com/go-pkgz/rest/rest.go +++ b/vendor/github.com/go-pkgz/rest/rest.go @@ -5,6 +5,7 @@ import ( "bytes" "encoding/json" "net/http" + "time" "github.com/pkg/errors" ) @@ -67,3 +68,33 @@ func renderJSONWithStatus(w http.ResponseWriter, data interface{}, code int) { w.WriteHeader(code) _, _ = w.Write(buf.Bytes()) } + +// ParseFromTo parses from and to query params of the request +func ParseFromTo(r *http.Request) (from, to time.Time, err error) { + parseTimeStamp := func(ts string) (time.Time, error) { + formats := []string{ + "2006-01-02T15:04:05.000000000", + "2006-01-02T15:04:05", + "2006-01-02T15:04", + "20060102", + time.RFC3339, + time.RFC3339Nano, + } + + for _, f := range formats { + if t, e := time.Parse(f, ts); e == nil { + return t, nil + } + } + return time.Time{}, errors.Errorf("can't parse date %q", ts) + } + + if from, err = parseTimeStamp(r.URL.Query().Get("from")); err != nil { + return from, to, errors.Wrap(err, "incorrect from time") + } + + if to, err = parseTimeStamp(r.URL.Query().Get("to")); err != nil { + return from, to, errors.Wrap(err, "incorrect to time") + } + return from, to, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b182334..6ab9107 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -26,10 +26,11 @@ github.com/go-pkgz/lgr ## explicit; go 1.12 github.com/go-pkgz/repeater github.com/go-pkgz/repeater/strategy -# github.com/go-pkgz/rest v1.12.2 +# github.com/go-pkgz/rest v1.14.0 ## explicit; go 1.16 github.com/go-pkgz/rest github.com/go-pkgz/rest/logger +github.com/go-pkgz/rest/realip # github.com/golang/protobuf v1.5.2 ## explicit; go 1.9 github.com/golang/protobuf/proto