update github.com/go-pkgz/rest

This commit is contained in:
Umputun 2022-04-06 11:41:15 -05:00
parent cda43cfb5c
commit bca30df215
8 changed files with 172 additions and 21 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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.

View File

@ -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)
}
@ -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

View File

@ -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)
}

80
vendor/github.com/go-pkgz/rest/realip/real.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

3
vendor/modules.txt vendored
View File

@ -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