mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-20 04:42:33 +02:00
enables add-x-forwarded by default, added hide-x-forwarded
This commit is contained in:
parent
9065d990e5
commit
ebedbc931f
4 changed files with 179 additions and 72 deletions
|
@ -9,12 +9,15 @@
|
|||
- [Available middlewares](#available-middlewares)
|
||||
- [Redirect http](#redirect-http)
|
||||
- [Custom error pages](#custom-error-pages)
|
||||
- [Real IP](#real-ip)
|
||||
- [Custom](#custom)
|
||||
- [Cloudflare](#cloudflare)
|
||||
- [Modify request or response](#modify-request-or-response)
|
||||
- [Set headers](#set-headers)
|
||||
- [Add headers](#add-headers)
|
||||
- [Hide headers](#hide-headers)
|
||||
- [X-Forwarded-\* Headers](#x-forwarded--headers)
|
||||
- [Add X-Forwarded-\*](#add-x-forwarded-)
|
||||
- [Hide X-Forwarded-\*](#hide-x-forwarded-)
|
||||
- [Set X-Forwarded-\*](#set-x-forwarded-)
|
||||
- [Forward Authorization header (experimental)](#forward-authorization-header-experimental)
|
||||
- [Examples](#examples)
|
||||
|
@ -104,6 +107,67 @@ location / {
|
|||
|
||||
[🔼Back to top](#table-of-content)
|
||||
|
||||
### Real IP
|
||||
|
||||
Check https://nginx.org/en/docs/http/ngx_http_realip_module.html for explaination of options
|
||||
|
||||
#### Custom
|
||||
|
||||
```yaml
|
||||
# docker labels
|
||||
proxy.app1.middlewares.real_ip.header: X-Real-IP
|
||||
proxy.app1.middlewares.real_ip.from: |
|
||||
- 127.0.0.1
|
||||
- 192.168.0.0/16
|
||||
- 10.0.0.0/8
|
||||
proxy.app1.middlewares.real_ip.recursive: true
|
||||
|
||||
# include file
|
||||
app1:
|
||||
middlewares:
|
||||
real_ip:
|
||||
header: X-Real-IP
|
||||
from:
|
||||
- 127.0.0.1
|
||||
- 192.168.0.0/16
|
||||
- 10.0.0.0/8
|
||||
recursive: true
|
||||
```
|
||||
|
||||
nginx equivalent:
|
||||
```nginx
|
||||
location / {
|
||||
set_real_ip_from 127.0.0.1;
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
set_real_ip_from 10.0.0.0/8;
|
||||
|
||||
real_ip_header X-Real-IP;
|
||||
real_ip_recursive on;
|
||||
}
|
||||
```
|
||||
|
||||
#### Cloudflare
|
||||
|
||||
This is a preset for Cloudflare
|
||||
|
||||
- `header`: `CF-Connecting-IP`
|
||||
- `from`: CIDR List of Cloudflare IPs from (updated every hour)
|
||||
- https://www.cloudflare.com/ips-v4
|
||||
- https://www.cloudflare.com/ips-v6
|
||||
- `recursive`: true
|
||||
|
||||
```yaml
|
||||
# docker labels
|
||||
proxy.app1.middlewares.cloudflare_real_ip:
|
||||
|
||||
# include file
|
||||
app1:
|
||||
middlewares:
|
||||
cloudflare_real_ip:
|
||||
```
|
||||
|
||||
[🔼Back to top](#table-of-content)
|
||||
|
||||
### Modify request or response
|
||||
|
||||
```yaml
|
||||
|
@ -199,21 +263,23 @@ location / {
|
|||
}
|
||||
```
|
||||
|
||||
[🔼Back to top](#table-of-content)
|
||||
|
||||
### X-Forwarded-* Headers
|
||||
|
||||
#### Add X-Forwarded-*
|
||||
#### Hide X-Forwarded-*
|
||||
|
||||
Append `X-Forwarded-*` headers to existing headers
|
||||
Remove `Forwarded` and `X-Forwarded-*` headers before request
|
||||
|
||||
```yaml
|
||||
# docker labels
|
||||
proxy.app1.middlewares.modify_request.add_x_forwarded:
|
||||
proxy.app1.middlewares.modify_request.hide_x_forwarded:
|
||||
|
||||
# include file
|
||||
app1:
|
||||
middlewares:
|
||||
modify_request:
|
||||
add_x_forwarded:
|
||||
hide_x_forwarded:
|
||||
```
|
||||
|
||||
#### Set X-Forwarded-*
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
D "github.com/yusing/go-proxy/internal/docker"
|
||||
)
|
||||
|
||||
|
@ -18,7 +19,7 @@ func Get(name string) (middleware *Middleware, ok bool) {
|
|||
func init() {
|
||||
middlewares = map[string]*Middleware{
|
||||
"set_x_forwarded": SetXForwarded,
|
||||
"add_x_forwarded": AddXForwarded,
|
||||
"hide_x_forwarded": HideXForwarded,
|
||||
"redirect_http": RedirectHTTP,
|
||||
"forward_auth": ForwardAuth.m,
|
||||
"modify_response": ModifyResponse.m,
|
||||
|
@ -44,4 +45,28 @@ func init() {
|
|||
m.name = names[0]
|
||||
}
|
||||
}
|
||||
// TODO: seperate from init()
|
||||
// b := E.NewBuilder("failed to load middlewares")
|
||||
// middlewareDefs, err := U.ListFiles(common.MiddlewareDefsBasePath, 0)
|
||||
// if err != nil {
|
||||
// logrus.Errorf("failed to list middleware definitions: %s", err)
|
||||
// return
|
||||
// }
|
||||
// for _, defFile := range middlewareDefs {
|
||||
// mws, err := BuildMiddlewaresFromYAML(defFile)
|
||||
// for name, m := range mws {
|
||||
// if _, ok := middlewares[name]; ok {
|
||||
// b.Add(E.Duplicated("middleware", name))
|
||||
// continue
|
||||
// }
|
||||
// middlewares[name] = m
|
||||
// logger.Infof("middleware %s loaded from %s", name, path.Base(defFile))
|
||||
// }
|
||||
// b.Add(err.Subject(defFile))
|
||||
// }
|
||||
// if b.HasError() {
|
||||
// logger.Error(b.Build())
|
||||
// }
|
||||
}
|
||||
|
||||
var logger = logrus.WithField("module", "middlewares")
|
||||
|
|
|
@ -2,34 +2,16 @@ package middleware
|
|||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var AddXForwarded = &Middleware{
|
||||
rewrite: func(req *Request) {
|
||||
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil {
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
} else {
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
}
|
||||
req.Header.Set("X-Forwarded-Host", req.Host)
|
||||
if req.TLS == nil {
|
||||
req.Header.Set("X-Forwarded-Proto", "http")
|
||||
} else {
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var SetXForwarded = &Middleware{
|
||||
rewrite: func(req *Request) {
|
||||
req.Header.Del("Forwarded")
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
req.Header.Del("X-Forwarded-Host")
|
||||
req.Header.Del("X-Forwarded-Proto")
|
||||
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err == nil {
|
||||
prior := req.Header["X-Forwarded-For"]
|
||||
if len(prior) > 0 {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
req.Header.Set("X-Forwarded-For", clientIP)
|
||||
} else {
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
|
@ -42,3 +24,12 @@ var SetXForwarded = &Middleware{
|
|||
}
|
||||
},
|
||||
}
|
||||
|
||||
var HideXForwarded = &Middleware{
|
||||
rewrite: func(req *Request) {
|
||||
req.Header.Del("Forwarded")
|
||||
req.Header.Del("X-Forwarded-For")
|
||||
req.Header.Del("X-Forwarded-Host")
|
||||
req.Header.Del("X-Forwarded-Proto")
|
||||
},
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
|
@ -65,28 +67,35 @@ type ProxyRequest struct {
|
|||
// 1xx responses are forwarded to the client if the underlying
|
||||
// transport supports ClientTrace.Got1xxResponse.
|
||||
type ReverseProxy struct {
|
||||
// Rewrite must be a function which modifies
|
||||
// Director is a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Rewrite must not access the provided ProxyRequest
|
||||
// or its contents after returning.
|
||||
// Director must not access the provided Request
|
||||
// after returning.
|
||||
//
|
||||
// The Forwarded, X-Forwarded, X-Forwarded-Host,
|
||||
// and X-Forwarded-Proto headers are removed from the
|
||||
// outbound request before Rewrite is called. See also
|
||||
// the ProxyRequest.SetXForwarded method.
|
||||
// By default, the X-Forwarded-For header is set to the
|
||||
// value of the client IP address. If an X-Forwarded-For
|
||||
// header already exists, the client IP is appended to the
|
||||
// existing values. As a special case, if the header
|
||||
// exists in the Request.Header map but has a nil value
|
||||
// (such as when set by the Director func), the X-Forwarded-For
|
||||
// header is not modified.
|
||||
//
|
||||
// Unparsable query parameters are removed from the
|
||||
// outbound request before Rewrite is called.
|
||||
// The Rewrite function may copy the inbound URL's
|
||||
// RawQuery to the outbound URL to preserve the original
|
||||
// parameter string. Note that this can lead to security
|
||||
// issues if the proxy's interpretation of query parameters
|
||||
// does not match that of the downstream server.
|
||||
// To prevent IP spoofing, be sure to delete any pre-existing
|
||||
// X-Forwarded-For header coming from the client or
|
||||
// an untrusted proxy.
|
||||
//
|
||||
// Hop-by-hop headers are removed from the request after
|
||||
// Director returns, which can remove headers added by
|
||||
// Director. Use a Rewrite function instead to ensure
|
||||
// modifications to the request are preserved.
|
||||
//
|
||||
// Unparsable query parameters are removed from the outbound
|
||||
// request if Request.Form is set after Director returns.
|
||||
//
|
||||
// At most one of Rewrite or Director may be set.
|
||||
Rewrite func(*ProxyRequest)
|
||||
Director func(*http.Request)
|
||||
|
||||
// The transport used to perform proxy requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
|
@ -106,13 +115,6 @@ type ReverseProxy struct {
|
|||
ServeHTTP http.HandlerFunc
|
||||
}
|
||||
|
||||
// A BufferPool is an interface for getting and returning temporary
|
||||
// byte slices for use by [io.CopyBuffer].
|
||||
type BufferPool interface {
|
||||
Get() []byte
|
||||
Put([]byte)
|
||||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
|
@ -169,10 +171,14 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
|||
//
|
||||
|
||||
func NewReverseProxy(target *url.URL, transport http.RoundTripper) *ReverseProxy {
|
||||
if transport == nil {
|
||||
panic("nil transport")
|
||||
}
|
||||
rp := &ReverseProxy{
|
||||
Rewrite: func(pr *ProxyRequest) {
|
||||
rewriteRequestURL(pr.Out, target)
|
||||
}, Transport: transport,
|
||||
Director: func(req *http.Request) {
|
||||
rewriteRequestURL(req, target)
|
||||
},
|
||||
Transport: transport,
|
||||
}
|
||||
rp.ServeHTTP = rp.serveHTTP
|
||||
return rp
|
||||
|
@ -190,6 +196,14 @@ func rewriteRequestURL(req *http.Request, target *url.URL) {
|
|||
}
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||
// Connection header field. These are the headers defined by the
|
||||
|
@ -207,14 +221,6 @@ var hopHeaders = []string{
|
|||
"Upgrade",
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, r *http.Request, err error, writeHeader bool) {
|
||||
logger.Errorf("http proxy to %s failed: %s", r.URL.String(), err)
|
||||
if writeHeader {
|
||||
|
@ -282,6 +288,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
|
||||
}
|
||||
|
||||
p.Director(outreq)
|
||||
outreq.Close = false
|
||||
|
||||
reqUpType := UpgradeType(outreq.Header)
|
||||
|
@ -313,12 +320,19 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
// outreq.Header.Del("X-Forwarded-Host")
|
||||
// outreq.Header.Del("X-Forwarded-Proto")
|
||||
|
||||
pr := &ProxyRequest{
|
||||
In: req,
|
||||
Out: outreq,
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
prior, ok := outreq.Header["X-Forwarded-For"]
|
||||
omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
|
||||
if len(prior) > 0 {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
if !omit {
|
||||
outreq.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
}
|
||||
p.Rewrite(pr)
|
||||
outreq = pr.Out
|
||||
|
||||
if _, ok := outreq.Header["User-Agent"]; !ok {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
|
@ -326,15 +340,21 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
outreq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
var (
|
||||
roundTripMutex sync.Mutex
|
||||
roundTripDone bool
|
||||
)
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
h := rw.Header()
|
||||
// copyHeader(h, http.Header(header))
|
||||
for k, vv := range header {
|
||||
for _, v := range vv {
|
||||
h.Add(k, v)
|
||||
}
|
||||
roundTripMutex.Lock()
|
||||
defer roundTripMutex.Unlock()
|
||||
if roundTripDone {
|
||||
// If RoundTrip has returned, don't try to further modify
|
||||
// the ResponseWriter's header map.
|
||||
return nil
|
||||
}
|
||||
h := rw.Header()
|
||||
copyHeader(h, http.Header(header))
|
||||
rw.WriteHeader(code)
|
||||
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
|
@ -345,6 +365,9 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
roundTripMutex.Lock()
|
||||
roundTripDone = true
|
||||
roundTripMutex.Unlock()
|
||||
if err != nil {
|
||||
p.errorHandler(rw, outreq, err, false)
|
||||
errMsg := err.Error()
|
||||
|
@ -371,6 +394,8 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
RemoveHopByHopHeaders(res.Header)
|
||||
|
||||
if !p.modifyResponse(rw, res, outreq) {
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue