mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-21 04:52:35 +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)
|
- [Available middlewares](#available-middlewares)
|
||||||
- [Redirect http](#redirect-http)
|
- [Redirect http](#redirect-http)
|
||||||
- [Custom error pages](#custom-error-pages)
|
- [Custom error pages](#custom-error-pages)
|
||||||
|
- [Real IP](#real-ip)
|
||||||
|
- [Custom](#custom)
|
||||||
|
- [Cloudflare](#cloudflare)
|
||||||
- [Modify request or response](#modify-request-or-response)
|
- [Modify request or response](#modify-request-or-response)
|
||||||
- [Set headers](#set-headers)
|
- [Set headers](#set-headers)
|
||||||
- [Add headers](#add-headers)
|
- [Add headers](#add-headers)
|
||||||
- [Hide headers](#hide-headers)
|
- [Hide headers](#hide-headers)
|
||||||
- [X-Forwarded-\* Headers](#x-forwarded--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-)
|
- [Set X-Forwarded-\*](#set-x-forwarded-)
|
||||||
- [Forward Authorization header (experimental)](#forward-authorization-header-experimental)
|
- [Forward Authorization header (experimental)](#forward-authorization-header-experimental)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
@ -104,6 +107,67 @@ location / {
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼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
|
### Modify request or response
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -199,21 +263,23 @@ location / {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
### X-Forwarded-* Headers
|
### 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
|
```yaml
|
||||||
# docker labels
|
# docker labels
|
||||||
proxy.app1.middlewares.modify_request.add_x_forwarded:
|
proxy.app1.middlewares.modify_request.hide_x_forwarded:
|
||||||
|
|
||||||
# include file
|
# include file
|
||||||
app1:
|
app1:
|
||||||
middlewares:
|
middlewares:
|
||||||
modify_request:
|
modify_request:
|
||||||
add_x_forwarded:
|
hide_x_forwarded:
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Set X-Forwarded-*
|
#### Set X-Forwarded-*
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
D "github.com/yusing/go-proxy/internal/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +19,7 @@ func Get(name string) (middleware *Middleware, ok bool) {
|
||||||
func init() {
|
func init() {
|
||||||
middlewares = map[string]*Middleware{
|
middlewares = map[string]*Middleware{
|
||||||
"set_x_forwarded": SetXForwarded,
|
"set_x_forwarded": SetXForwarded,
|
||||||
"add_x_forwarded": AddXForwarded,
|
"hide_x_forwarded": HideXForwarded,
|
||||||
"redirect_http": RedirectHTTP,
|
"redirect_http": RedirectHTTP,
|
||||||
"forward_auth": ForwardAuth.m,
|
"forward_auth": ForwardAuth.m,
|
||||||
"modify_response": ModifyResponse.m,
|
"modify_response": ModifyResponse.m,
|
||||||
|
@ -44,4 +45,28 @@ func init() {
|
||||||
m.name = names[0]
|
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 (
|
import (
|
||||||
"net"
|
"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{
|
var SetXForwarded = &Middleware{
|
||||||
rewrite: func(req *Request) {
|
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)
|
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
if err == nil {
|
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)
|
req.Header.Set("X-Forwarded-For", clientIP)
|
||||||
} else {
|
} else {
|
||||||
req.Header.Del("X-Forwarded-For")
|
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"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptrace"
|
"net/http/httptrace"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/net/http/httpguts"
|
"golang.org/x/net/http/httpguts"
|
||||||
|
@ -65,28 +67,35 @@ type ProxyRequest struct {
|
||||||
// 1xx responses are forwarded to the client if the underlying
|
// 1xx responses are forwarded to the client if the underlying
|
||||||
// transport supports ClientTrace.Got1xxResponse.
|
// transport supports ClientTrace.Got1xxResponse.
|
||||||
type ReverseProxy struct {
|
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
|
// the request into a new request to be sent
|
||||||
// using Transport. Its response is then copied
|
// using Transport. Its response is then copied
|
||||||
// back to the original client unmodified.
|
// back to the original client unmodified.
|
||||||
// Rewrite must not access the provided ProxyRequest
|
// Director must not access the provided Request
|
||||||
// or its contents after returning.
|
// after returning.
|
||||||
//
|
//
|
||||||
// The Forwarded, X-Forwarded, X-Forwarded-Host,
|
// By default, the X-Forwarded-For header is set to the
|
||||||
// and X-Forwarded-Proto headers are removed from the
|
// value of the client IP address. If an X-Forwarded-For
|
||||||
// outbound request before Rewrite is called. See also
|
// header already exists, the client IP is appended to the
|
||||||
// the ProxyRequest.SetXForwarded method.
|
// 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
|
// To prevent IP spoofing, be sure to delete any pre-existing
|
||||||
// outbound request before Rewrite is called.
|
// X-Forwarded-For header coming from the client or
|
||||||
// The Rewrite function may copy the inbound URL's
|
// an untrusted proxy.
|
||||||
// RawQuery to the outbound URL to preserve the original
|
//
|
||||||
// parameter string. Note that this can lead to security
|
// Hop-by-hop headers are removed from the request after
|
||||||
// issues if the proxy's interpretation of query parameters
|
// Director returns, which can remove headers added by
|
||||||
// does not match that of the downstream server.
|
// 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.
|
// At most one of Rewrite or Director may be set.
|
||||||
Rewrite func(*ProxyRequest)
|
Director func(*http.Request)
|
||||||
|
|
||||||
// The transport used to perform proxy requests.
|
// The transport used to perform proxy requests.
|
||||||
// If nil, http.DefaultTransport is used.
|
// If nil, http.DefaultTransport is used.
|
||||||
|
@ -106,13 +115,6 @@ type ReverseProxy struct {
|
||||||
ServeHTTP http.HandlerFunc
|
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 {
|
func singleJoiningSlash(a, b string) string {
|
||||||
aslash := strings.HasSuffix(a, "/")
|
aslash := strings.HasSuffix(a, "/")
|
||||||
bslash := strings.HasPrefix(b, "/")
|
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 {
|
func NewReverseProxy(target *url.URL, transport http.RoundTripper) *ReverseProxy {
|
||||||
|
if transport == nil {
|
||||||
|
panic("nil transport")
|
||||||
|
}
|
||||||
rp := &ReverseProxy{
|
rp := &ReverseProxy{
|
||||||
Rewrite: func(pr *ProxyRequest) {
|
Director: func(req *http.Request) {
|
||||||
rewriteRequestURL(pr.Out, target)
|
rewriteRequestURL(req, target)
|
||||||
}, Transport: transport,
|
},
|
||||||
|
Transport: transport,
|
||||||
}
|
}
|
||||||
rp.ServeHTTP = rp.serveHTTP
|
rp.ServeHTTP = rp.serveHTTP
|
||||||
return rp
|
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.
|
// 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
|
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||||
// Connection header field. These are the headers defined by the
|
// Connection header field. These are the headers defined by the
|
||||||
|
@ -207,14 +221,6 @@ var hopHeaders = []string{
|
||||||
"Upgrade",
|
"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) {
|
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)
|
logger.Errorf("http proxy to %s failed: %s", r.URL.String(), err)
|
||||||
if writeHeader {
|
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
|
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.Director(outreq)
|
||||||
outreq.Close = false
|
outreq.Close = false
|
||||||
|
|
||||||
reqUpType := UpgradeType(outreq.Header)
|
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-Host")
|
||||||
// outreq.Header.Del("X-Forwarded-Proto")
|
// outreq.Header.Del("X-Forwarded-Proto")
|
||||||
|
|
||||||
pr := &ProxyRequest{
|
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
In: req,
|
// If we aren't the first proxy retain prior
|
||||||
Out: outreq,
|
// 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 _, ok := outreq.Header["User-Agent"]; !ok {
|
||||||
// If the outbound request doesn't have a User-Agent header set,
|
// 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", "")
|
outreq.Header.Set("User-Agent", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
roundTripMutex sync.Mutex
|
||||||
|
roundTripDone bool
|
||||||
|
)
|
||||||
trace := &httptrace.ClientTrace{
|
trace := &httptrace.ClientTrace{
|
||||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||||
|
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()
|
h := rw.Header()
|
||||||
// copyHeader(h, http.Header(header))
|
copyHeader(h, http.Header(header))
|
||||||
for k, vv := range header {
|
|
||||||
for _, v := range vv {
|
|
||||||
h.Add(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
|
|
||||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
// 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))
|
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
|
||||||
|
|
||||||
res, err := transport.RoundTrip(outreq)
|
res, err := transport.RoundTrip(outreq)
|
||||||
|
roundTripMutex.Lock()
|
||||||
|
roundTripDone = true
|
||||||
|
roundTripMutex.Unlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.errorHandler(rw, outreq, err, false)
|
p.errorHandler(rw, outreq, err, false)
|
||||||
errMsg := err.Error()
|
errMsg := err.Error()
|
||||||
|
@ -371,6 +394,8 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RemoveHopByHopHeaders(res.Header)
|
||||||
|
|
||||||
if !p.modifyResponse(rw, res, outreq) {
|
if !p.modifyResponse(rw, res, outreq) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue