mirror of https://github.com/gofiber/fiber.git
🔥 Feature: Add TrustedProxies feature (#1397)
* 🔥 Feature: Add TrustedProxies feature
Add TrustedProxies feature to prevent HTTP header spoofing.
* Update ctx.go
* Update app.go
* Update app.go
Co-authored-by: hi019 <65871571+hi019@users.noreply.github.com>
pull/1425/head
parent
e082880297
commit
5ba78cd24a
33
app.go
33
app.go
|
@ -295,6 +295,34 @@ type Config struct {
|
||||||
//
|
//
|
||||||
// Default: NetworkTCP4
|
// Default: NetworkTCP4
|
||||||
Network string
|
Network string
|
||||||
|
|
||||||
|
// If you find yourself behind some sort of proxy, like a load balancer,
|
||||||
|
// then certain header information may be sent to you using special X-Forwarded-* headers or the Forwarded header.
|
||||||
|
// For example, the Host HTTP header is usually used to return the requested host.
|
||||||
|
// But when you’re behind a proxy, the actual host may be stored in an X-Forwarded-Host header.
|
||||||
|
//
|
||||||
|
// If you are behind a proxy, you should enable TrustedProxyCheck to prevent header spoofing.
|
||||||
|
// If you enable EnableTrustedProxyCheck and leave TrustedProxies empty Fiber will skip
|
||||||
|
// all headers that could be spoofed.
|
||||||
|
// If request ip in TrustedProxies whitelist then:
|
||||||
|
// 1. c.Protocol() get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header
|
||||||
|
// 2. c.IP() get value from ProxyHeader header.
|
||||||
|
// 3. c.Hostname() get value from X-Forwarded-Host header
|
||||||
|
// But if request ip NOT in Trusted Proxies whitelist then:
|
||||||
|
// 1. c.Protocol() WON't get value from X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Ssl or X-Url-Scheme header,
|
||||||
|
// will return https in case when tls connection is handled by the app, of http otherwise
|
||||||
|
// 2. c.IP() WON'T get value from ProxyHeader header, will return RemoteIP() from fasthttp context
|
||||||
|
// 3. c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host()
|
||||||
|
// will be used to get the hostname.
|
||||||
|
//
|
||||||
|
// Default: false
|
||||||
|
EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check"`
|
||||||
|
|
||||||
|
// Read EnableTrustedProxyCheck doc.
|
||||||
|
//
|
||||||
|
// Default: []string
|
||||||
|
TrustedProxies []string `json:"trusted_proxies"`
|
||||||
|
trustedProxiesMap map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Static defines configuration options when defining static assets.
|
// Static defines configuration options when defining static assets.
|
||||||
|
@ -417,6 +445,11 @@ func New(config ...Config) *App {
|
||||||
app.config.Network = NetworkTCP4
|
app.config.Network = NetworkTCP4
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.config.trustedProxiesMap = make(map[string]struct{}, len(app.config.TrustedProxies))
|
||||||
|
for _, ip := range app.config.TrustedProxies {
|
||||||
|
app.config.trustedProxiesMap[ip] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
app.init()
|
app.init()
|
||||||
|
|
||||||
|
|
25
ctx.go
25
ctx.go
|
@ -526,18 +526,26 @@ func (c *Ctx) Get(key string, defaultValue ...string) string {
|
||||||
return defaultString(c.app.getString(c.fasthttp.Request.Header.Peek(key)), defaultValue)
|
return defaultString(c.app.getString(c.fasthttp.Request.Header.Peek(key)), defaultValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hostname contains the hostname derived from the Host HTTP header.
|
// Hostname contains the hostname derived from the X-Forwarded-Host or Host HTTP header.
|
||||||
// Returned value is only valid within the handler. Do not store any references.
|
// Returned value is only valid within the handler. Do not store any references.
|
||||||
// Make copies or use the Immutable setting instead.
|
// Make copies or use the Immutable setting instead.
|
||||||
|
// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
|
||||||
func (c *Ctx) Hostname() string {
|
func (c *Ctx) Hostname() string {
|
||||||
|
if c.IsProxyTrusted() {
|
||||||
|
if host := c.Get(HeaderXForwardedHost); len(host) > 0 {
|
||||||
|
return host
|
||||||
|
}
|
||||||
|
}
|
||||||
return c.app.getString(c.fasthttp.Request.URI().Host())
|
return c.app.getString(c.fasthttp.Request.URI().Host())
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP returns the remote IP address of the request.
|
// IP returns the remote IP address of the request.
|
||||||
|
// Please use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
|
||||||
func (c *Ctx) IP() string {
|
func (c *Ctx) IP() string {
|
||||||
if len(c.app.config.ProxyHeader) > 0 {
|
if c.IsProxyTrusted() && len(c.app.config.ProxyHeader) > 0 {
|
||||||
return c.Get(c.app.config.ProxyHeader)
|
return c.Get(c.app.config.ProxyHeader)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.fasthttp.RemoteIP().String()
|
return c.fasthttp.RemoteIP().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,11 +747,15 @@ func (c *Ctx) Path(override ...string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol contains the request protocol string: http or https for TLS requests.
|
// Protocol contains the request protocol string: http or https for TLS requests.
|
||||||
|
// Use Config.EnableTrustedProxyCheck to prevent header spoofing, in case when your app is behind the proxy.
|
||||||
func (c *Ctx) Protocol() string {
|
func (c *Ctx) Protocol() string {
|
||||||
if c.fasthttp.IsTLS() {
|
if c.fasthttp.IsTLS() {
|
||||||
return "https"
|
return "https"
|
||||||
}
|
}
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
|
if !c.IsProxyTrusted() {
|
||||||
|
return scheme
|
||||||
|
}
|
||||||
c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
|
c.fasthttp.Request.Header.VisitAll(func(key, val []byte) {
|
||||||
if len(key) < 12 {
|
if len(key) < 12 {
|
||||||
return // X-Forwarded-
|
return // X-Forwarded-
|
||||||
|
@ -1195,3 +1207,12 @@ func (c *Ctx) configDependentPaths() {
|
||||||
c.treePath = c.detectionPath[:3]
|
c.treePath = c.detectionPath[:3]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Ctx) IsProxyTrusted() bool {
|
||||||
|
if !c.app.config.EnableTrustedProxyCheck {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
_, trustProxy := c.app.config.trustedProxiesMap[c.fasthttp.RemoteIP().String()]
|
||||||
|
return trustProxy
|
||||||
|
}
|
||||||
|
|
108
ctx_test.go
108
ctx_test.go
|
@ -825,6 +825,42 @@ func Test_Ctx_Hostname(t *testing.T) {
|
||||||
utils.AssertEqual(t, "google.com", c.Hostname())
|
utils.AssertEqual(t, "google.com", c.Hostname())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Hostname_Untrusted
|
||||||
|
func Test_Ctx_Hostname_UntrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
// Don't trust any proxy
|
||||||
|
{
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{}})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
c.Request().SetRequestURI("http://google.com/test")
|
||||||
|
c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
|
||||||
|
utils.AssertEqual(t, "google.com", c.Hostname())
|
||||||
|
app.ReleaseCtx(c)
|
||||||
|
}
|
||||||
|
// Trust to specific proxy list
|
||||||
|
{
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
c.Request().SetRequestURI("http://google.com/test")
|
||||||
|
c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
|
||||||
|
utils.AssertEqual(t, "google.com", c.Hostname())
|
||||||
|
app.ReleaseCtx(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Hostname_Trusted
|
||||||
|
func Test_Ctx_Hostname_TrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
{
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0", "0.8.0.1"}})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
c.Request().SetRequestURI("http://google.com/test")
|
||||||
|
c.Request().Header.Set(HeaderXForwardedHost, "google1.com")
|
||||||
|
utils.AssertEqual(t, "google1.com", c.Hostname())
|
||||||
|
app.ReleaseCtx(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_IP
|
// go test -run Test_Ctx_IP
|
||||||
func Test_Ctx_IP(t *testing.T) {
|
func Test_Ctx_IP(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -843,6 +879,26 @@ func Test_Ctx_IP_ProxyHeader(t *testing.T) {
|
||||||
utils.AssertEqual(t, "", c.IP())
|
utils.AssertEqual(t, "", c.IP())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_IP_UntrustedProxy
|
||||||
|
func Test_Ctx_IP_UntrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}, ProxyHeader: HeaderXForwardedFor})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1")
|
||||||
|
defer app.ReleaseCtx(c)
|
||||||
|
utils.AssertEqual(t, "0.0.0.0", c.IP())
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_IP_TrustedProxy
|
||||||
|
func Test_Ctx_IP_TrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}, ProxyHeader: HeaderXForwardedFor})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
c.Request().Header.Set(HeaderXForwardedFor, "0.0.0.1")
|
||||||
|
defer app.ReleaseCtx(c)
|
||||||
|
utils.AssertEqual(t, "0.0.0.1", c.IP())
|
||||||
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_IPs -parallel
|
// go test -run Test_Ctx_IPs -parallel
|
||||||
func Test_Ctx_IPs(t *testing.T) {
|
func Test_Ctx_IPs(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -1169,6 +1225,58 @@ func Benchmark_Ctx_Protocol(b *testing.B) {
|
||||||
utils.AssertEqual(b, "http", res)
|
utils.AssertEqual(b, "http", res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Protocol_TrustedProxy
|
||||||
|
func Test_Ctx_Protocol_TrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.0.0.0"}})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
defer app.ReleaseCtx(c)
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedProto, "https")
|
||||||
|
utils.AssertEqual(t, "https", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedProtocol, "https")
|
||||||
|
utils.AssertEqual(t, "https", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedSsl, "on")
|
||||||
|
utils.AssertEqual(t, "https", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXUrlScheme, "https")
|
||||||
|
utils.AssertEqual(t, "https", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
}
|
||||||
|
|
||||||
|
// go test -run Test_Ctx_Protocol_UnTrustedProxy
|
||||||
|
func Test_Ctx_Protocol_UnTrustedProxy(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
app := New(Config{EnableTrustedProxyCheck: true, TrustedProxies: []string{"0.8.0.1"}})
|
||||||
|
c := app.AcquireCtx(&fasthttp.RequestCtx{})
|
||||||
|
defer app.ReleaseCtx(c)
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedProto, "https")
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedProtocol, "https")
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXForwardedSsl, "on")
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
c.Request().Header.Set(HeaderXUrlScheme, "https")
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
c.Request().Header.Reset()
|
||||||
|
|
||||||
|
utils.AssertEqual(t, "http", c.Protocol())
|
||||||
|
}
|
||||||
|
|
||||||
// go test -run Test_Ctx_Query
|
// go test -run Test_Ctx_Query
|
||||||
func Test_Ctx_Query(t *testing.T) {
|
func Test_Ctx_Query(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
Loading…
Reference in New Issue