diff --git a/app.go b/app.go index e0240d3c16..5c9cd73d03 100644 --- a/app.go +++ b/app.go @@ -344,6 +344,9 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa // 3. c.Host() and c.Hostname() WON'T get value from X-Forwarded-Host header, fasthttp.Request.URI().Host() // will be used to get the hostname. // + // If you wish to trust all internal ip addresses (loopback, private, and link-local unicast) + // without manually adding them to TrustedProxies whitelist, enable TrustInternalIPs. + // // Default: false EnableTrustedProxyCheck bool `json:"enable_trusted_proxy_check"` @@ -354,6 +357,11 @@ type Config struct { //nolint:govet // Aligning the struct fields is not necessa trustedProxiesMap map[string]struct{} trustedProxyRanges []*net.IPNet + // Read EnableTrustedProxyCheck doc. + // + // Default: false + TrustInternalIPs bool `json:"trust_internal_ips"` + // If set to true, c.IP() and c.IPs() will validate IP addresses before returning them. // Also, c.IP() will return only the first valid IP rather than just the raw header // WARNING: this has a performance cost associated with it. diff --git a/ctx.go b/ctx.go index 4d7417ee2a..d7a65bbb53 100644 --- a/ctx.go +++ b/ctx.go @@ -1828,6 +1828,10 @@ func (c *DefaultCtx) IsProxyTrusted() bool { ip := c.fasthttp.RemoteIP() + if c.app.config.TrustInternalIPs && c.isInternalHost(ip) { + return true + } + if _, trusted := c.app.config.trustedProxiesMap[ip.String()]; trusted { return true } @@ -1841,9 +1845,14 @@ func (c *DefaultCtx) IsProxyTrusted() bool { return false } +// isInternalHost will return true if address is an internal address. +func (*DefaultCtx) isInternalHost(ip net.IP) bool { + return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() +} + var localHosts = [...]string{"127.0.0.1", "::1"} -// IsLocalHost will return true if address is a localhost address. +// isLocalHost will return true if address is a localhost address. func (*DefaultCtx) isLocalHost(address string) bool { for _, h := range localHosts { if address == h { diff --git a/ctx_test.go b/ctx_test.go index 6dcd74109e..d207710c04 100644 --- a/ctx_test.go +++ b/ctx_test.go @@ -1714,6 +1714,16 @@ func Test_Ctx_IsProxyTrusted(t *testing.T) { c := app.AcquireCtx(&fasthttp.RequestCtx{}) require.False(t, c.IsProxyTrusted()) } + + { + app := New(Config{ + EnableTrustedProxyCheck: true, + + TrustInternalIPs: true, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + require.False(t, c.IsProxyTrusted()) + } } // go test -run Test_Ctx_Hostname @@ -6351,4 +6361,40 @@ func Benchmark_Ctx_IsProxyTrusted(b *testing.B) { app.ReleaseCtx(c) }) }) + + // Scenario with trusted proxy check with TrustInternalIPs true + b.Run("WithProxyCheckOnlyTrustInternalIPs", func(b *testing.B) { + app := New(Config{ + EnableTrustedProxyCheck: true, + TrustInternalIPs: true, + }) + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/test") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + b.ReportAllocs() + b.ResetTimer() + for n := 0; n < b.N; n++ { + c.IsProxyTrusted() + } + app.ReleaseCtx(c) + }) + + // Scenario with trusted proxy check with all subnets with TrustInternalIPs true in parallel + b.Run("WithProxyCheckParallelOnlyTrustInternalIPs", func(b *testing.B) { + app := New(Config{ + EnableTrustedProxyCheck: true, + TrustInternalIPs: true, + }) + b.ReportAllocs() + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + c := app.AcquireCtx(&fasthttp.RequestCtx{}) + c.Request().SetRequestURI("http://google.com/") + c.Request().Header.Set(HeaderXForwardedHost, "google1.com") + for pb.Next() { + c.IsProxyTrusted() + } + app.ReleaseCtx(c) + }) + }) } diff --git a/docs/api/ctx.md b/docs/api/ctx.md index 2640512a3e..60e98dcfee 100644 --- a/docs/api/ctx.md +++ b/docs/api/ctx.md @@ -812,7 +812,7 @@ app.Get("/", func(c fiber.Ctx) error { ## IsFromLocal -Returns true if request came from localhost +Returns true if request came from localhost. ```go title="Signature" func (c Ctx) IsFromLocal() bool { @@ -831,7 +831,7 @@ app.Get("/", func(c fiber.Ctx) error { ## IsProxyTrusted Checks trustworthiness of remote ip. -If [`EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) false, it returns true +If [`EnableTrustedProxyCheck`](fiber.md#enabletrustedproxycheck) false, it returns true. IsProxyTrusted can check remote ip by proxy ranges and ip map. ```go title="Signature" @@ -845,6 +845,8 @@ app := fiber.New(fiber.Config{ EnableTrustedProxyCheck: true, // TrustedProxies is a list of trusted proxy IP addresses TrustedProxies: []string{"0.8.0.0", "0.8.0.1"}, + // TrustInternalIPs can be used to trust all private/loopback/linklocal-unicast addresses + TrustInternalIPs: true, }) diff --git a/docs/api/fiber.md b/docs/api/fiber.md index 16f23b9e61..bd3190deaf 100644 --- a/docs/api/fiber.md +++ b/docs/api/fiber.md @@ -48,7 +48,7 @@ app := fiber.New(fiber.Config{ | BodyLimit | `int` | Sets the maximum allowed size for a request body, if the size exceeds the configured limit, it sends `413 - Request Entity Too Large` response. | `4 * 1024 * 1024` | | CaseSensitive | `bool` | When enabled, `/Foo` and `/foo` are different routes. When disabled, `/Foo`and `/foo` are treated the same. | `false` | | ColorScheme | [`Colors`](https://github.com/gofiber/fiber/blob/master/color.go) | You can define custom color scheme. They'll be used for startup message, route list and some middlewares. | [`DefaultColors`](https://github.com/gofiber/fiber/blob/master/color.go) | -| CompressedFileSuffixes | `map[string]string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `{"gzip": ".fiber.gz", "br": ".fiber.br", "zstd": ".fiber.zst"}` | +| CompressedFileSuffixes | `map[string]string` | Adds a suffix to the original file name and tries saving the resulting compressed file under the new file name. | `{"gzip": ".fiber.gz", "br": ".fiber.br", "zstd": ".fiber.zst"}` | | Concurrency | `int` | Maximum number of concurrent connections. | `256 * 1024` | | DisableDefaultContentType | `bool` | When set to true, causes the default Content-Type header to be excluded from the Response. | `false` | | DisableDefaultDate | `bool` | When set to true causes the default date header to be excluded from the response. | `false` | @@ -75,6 +75,7 @@ app := fiber.New(fiber.Config{ | StrictRouting | `bool` | When enabled, the router treats `/foo` and `/foo/` as different. Otherwise, the router treats `/foo` and `/foo/` as the same. | `false` | | StructValidator | `StructValidator` | If you want to validate header/form/query... automatically when to bind, you can define struct validator. Fiber doesn't have default validator, so it'll skip validator step if you don't use any validator. | `nil` | | TrustedProxies | `[]string` | Contains the list of trusted proxy IP's. Look at `EnableTrustedProxyCheck` doc.

It can take IP or IP range addresses. | `nil` | +| TrustInternalIPs | `bool` | If `EnableTrustedProxyCheck` is `true`, automatically trust all private, loopback, and link-local unicast IP addresses. | `false` | | UnescapePath | `bool` | Converts all encoded characters in the route back before setting the path for the context, so that the routing can also work with URL encoded special characters | `false` | | Views | `Views` | Views is the interface that wraps the Render function. See our **Template Middleware** for supported engines. | `nil` | | ViewsLayout | `string` | Views Layout is the global layout for all template render until override on Render function. See our **Template Middleware** for supported engines. | `""` |